1010import java .util .concurrent .ScheduledExecutorService ;
1111import java .util .concurrent .TimeUnit ;
1212import java .util .concurrent .atomic .AtomicBoolean ;
13+ import java .util .concurrent .atomic .AtomicReference ;
1314import java .util .concurrent .locks .ReentrantLock ;
1415
1516public class ConfigService implements Closeable {
1617
1718 private static final String CACHE_BASE = "%s_" + Constants .CONFIG_JSON_NAME + "_" + Constants .SERIALIZATION_FORMAT_VERSION ;
1819
19- private Entry cachedEntry = Entry .EMPTY ;
20- private String cachedEntryString = "" ;
20+ private final AtomicReference <Entry > cachedEntry = new AtomicReference <>(Entry .EMPTY );
2121 private final ConfigCache cache ;
2222 private final String cacheKey ;
2323 private final ConfigFetcher configFetcher ;
@@ -55,29 +55,29 @@ public ConfigService(String sdkKey,
5555
5656 this .initScheduler = Executors .newSingleThreadScheduledExecutor ();
5757 this .initScheduler .schedule (() -> {
58- lock . lock ();
59- try {
60- if ( initialized . compareAndSet ( false , true )) {
61- this .configCatHooks .invokeOnClientReady (determineCacheState ());
58+ if ( initialized . compareAndSet ( false , true )) {
59+ lock . lock ();
60+ try {
61+ this .configCatHooks .invokeOnClientReady (determineCacheState (cachedEntry . get () ));
6262 String message = ConfigCatLogMessages .getAutoPollMaxInitWaitTimeReached (autoPollingMode .getMaxInitWaitTimeSeconds ());
6363 this .logger .warn (4200 , message );
64- completeRunningTask (Result .error (message , cachedEntry ));
64+ completeRunningTask (Result .error (message , cachedEntry .get ()));
65+ } finally {
66+ lock .unlock ();
6567 }
66- } finally {
67- lock .unlock ();
6868 }
6969 }, autoPollingMode .getMaxInitWaitTimeSeconds (), TimeUnit .SECONDS );
7070
7171 } else {
7272 // Sync up with cache before reporting ready state
73- cachedEntry = readCache ();
73+ cachedEntry . set ( readCache () );
7474 setInitialized ();
7575 }
7676 }
7777
7878 private void setInitialized () {
7979 if (initialized .compareAndSet (false , true )) {
80- configCatHooks .invokeOnClientReady (determineCacheState ());
80+ configCatHooks .invokeOnClientReady (determineCacheState (cachedEntry . get () ));
8181 }
8282 }
8383
@@ -121,27 +121,27 @@ public CompletableFuture<SettingResult> getSettings() {
121121 }
122122
123123 private CompletableFuture <Result <Entry >> fetchIfOlder (long threshold , boolean preferCached ) {
124+ // Sync up with the cache and use it when it's not expired.
125+ Entry fromCache = readCache ();
126+ if (!fromCache .isEmpty () && !fromCache .getETag ().equals (cachedEntry .get ().getETag ()) && fromCache .getFetchTime () > cachedEntry .get ().getFetchTime ()) {
127+ configCatHooks .invokeOnConfigChanged (fromCache .getConfig ().getEntries ());
128+ cachedEntry .set (fromCache );
129+ }
130+ // Cache isn't expired
131+ if (cachedEntry .get ().getFetchTime () > threshold ) {
132+ setInitialized ();
133+ return CompletableFuture .completedFuture (Result .success (cachedEntry .get ()));
134+ }
135+ // If we are in offline mode or the caller prefers cached values, do not initiate fetch.
136+ if (offline .get () || preferCached ) {
137+ return CompletableFuture .completedFuture (Result .success (cachedEntry .get ()));
138+ }
139+
124140 lock .lock ();
125141 try {
126- // Sync up with the cache and use it when it's not expired.
127- Entry fromCache = readCache ();
128- if (!fromCache .isEmpty () && !fromCache .getETag ().equals (cachedEntry .getETag ())) {
129- configCatHooks .invokeOnConfigChanged (fromCache .getConfig ().getEntries ());
130- cachedEntry = fromCache ;
131- }
132- // Cache isn't expired
133- if (!cachedEntry .isExpired (threshold )) {
134- setInitialized ();
135- return CompletableFuture .completedFuture (Result .success (cachedEntry ));
136- }
137- // If we are in offline mode or the caller prefers cached values, do not initiate fetch.
138- if (offline .get () || preferCached ) {
139- return CompletableFuture .completedFuture (Result .success (cachedEntry ));
140- }
141-
142142 if (runningTask == null ) { // No fetch is running, initiate a new one.
143143 runningTask = new CompletableFuture <>();
144- configFetcher .fetchAsync (cachedEntry .getETag ())
144+ configFetcher .fetchAsync (cachedEntry .get (). getETag ())
145145 .thenAccept (this ::processResponse );
146146 }
147147
@@ -194,22 +194,23 @@ public boolean isOffline() {
194194 }
195195
196196 private void processResponse (FetchResponse response ) {
197+ Entry previousEntry = cachedEntry .get ();
197198 lock .lock ();
198199 try {
199200 if (response .isFetched ()) {
200201 Entry entry = response .entry ();
201- cachedEntry = entry ;
202+ cachedEntry . set ( entry ) ;
202203 writeCache (entry );
203204 configCatHooks .invokeOnConfigChanged (entry .getConfig ().getEntries ());
204205 completeRunningTask (Result .success (entry ));
205206 } else {
206207 if (response .isFetchTimeUpdatable ()) {
207- cachedEntry = cachedEntry . withFetchTime (System .currentTimeMillis ());
208- writeCache (cachedEntry );
208+ cachedEntry . set ( previousEntry . withFetchTime (System .currentTimeMillis () ));
209+ writeCache (cachedEntry . get () );
209210 }
210211 completeRunningTask (response .isFailed ()
211- ? Result .error (response .error (), cachedEntry )
212- : Result .success (cachedEntry ));
212+ ? Result .error (response .error (), cachedEntry . get () )
213+ : Result .success (cachedEntry . get () ));
213214 }
214215 setInitialized ();
215216 } finally {
@@ -225,10 +226,9 @@ private void completeRunningTask(Result<Entry> result) {
225226 private Entry readCache () {
226227 try {
227228 String cachedConfigJson = cache .read (cacheKey );
228- if (cachedConfigJson != null && cachedConfigJson .equals (cachedEntryString )) {
229+ if (cachedConfigJson != null && cachedConfigJson .equals (cachedEntry . get (). getCacheString () )) {
229230 return Entry .EMPTY ;
230231 }
231- cachedEntryString = cachedConfigJson ;
232232 Entry deserialized = Entry .fromString (cachedConfigJson );
233233 return deserialized == null || deserialized .getConfig () == null ? Entry .EMPTY : deserialized ;
234234 } catch (Exception e ) {
@@ -239,26 +239,24 @@ private Entry readCache() {
239239
240240 private void writeCache (Entry entry ) {
241241 try {
242- String configToCache = entry .serialize ();
243- cachedEntryString = configToCache ;
244- cache .write (cacheKey , configToCache );
242+ cache .write (cacheKey , entry .getCacheString ());
245243 } catch (Exception e ) {
246244 logger .error (2201 , ConfigCatLogMessages .CONFIG_SERVICE_CACHE_WRITE_ERROR , e );
247245 }
248246 }
249247
250- private ClientCacheState determineCacheState () {
251- if (cachedEntry .isEmpty ()) {
248+ private ClientCacheState determineCacheState (Entry cachedEntry ) {
249+ if (cachedEntry .isEmpty ()) {
252250 return ClientCacheState .NO_FLAG_DATA ;
253251 }
254- if (pollingMode instanceof ManualPollingMode ) {
252+ if (pollingMode instanceof ManualPollingMode ) {
255253 return ClientCacheState .HAS_CACHED_FLAG_DATA_ONLY ;
256- } else if (pollingMode instanceof LazyLoadingMode ) {
257- if (cachedEntry .isExpired (System .currentTimeMillis () - (((LazyLoadingMode )pollingMode ).getCacheRefreshIntervalInSeconds () * 1000L ))) {
254+ } else if (pollingMode instanceof LazyLoadingMode ) {
255+ if (cachedEntry .isExpired (System .currentTimeMillis () - (((LazyLoadingMode ) pollingMode ).getCacheRefreshIntervalInSeconds () * 1000L ))) {
258256 return ClientCacheState .HAS_CACHED_FLAG_DATA_ONLY ;
259257 }
260- } else if (pollingMode instanceof AutoPollingMode ) {
261- if (cachedEntry .isExpired (System .currentTimeMillis () - (((AutoPollingMode )pollingMode ).getAutoPollRateInSeconds () * 1000L ))) {
258+ } else if (pollingMode instanceof AutoPollingMode ) {
259+ if (cachedEntry .isExpired (System .currentTimeMillis () - (((AutoPollingMode ) pollingMode ).getAutoPollRateInSeconds () * 1000L ))) {
262260 return ClientCacheState .HAS_CACHED_FLAG_DATA_ONLY ;
263261 }
264262 }
0 commit comments