Skip to content

Commit

Permalink
Read cache upon each setting read (#40)
Browse files Browse the repository at this point in the history
* Read cache upon each setting read

* Update java-ci.yml
  • Loading branch information
z4kn4fein authored Nov 9, 2023
1 parent cf4ff05 commit da932c0
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/java-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v3
with:
java-version: 11
java-version: 17
distribution: zulu
cache: gradle

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=8.3.0
version=8.4.0
41 changes: 15 additions & 26 deletions src/main/java/com/configcat/ConfigService.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class ConfigService implements Closeable {
private ScheduledExecutorService pollScheduler;
private ScheduledExecutorService initScheduler;
private CompletableFuture<Result<Entry>> runningTask;
private boolean initialized = false;
private final AtomicBoolean initialized = new AtomicBoolean(false);
private final AtomicBoolean closed = new AtomicBoolean(false);
private final AtomicBoolean offline;
private final ReentrantLock lock = new ReentrantLock(true);
Expand Down Expand Up @@ -57,8 +57,7 @@ public ConfigService(String sdkKey,
this.initScheduler.schedule(() -> {
lock.lock();
try {
if (!initialized) {
initialized = true;
if (initialized.compareAndSet(false, true)) {
this.configCatHooks.invokeOnClientReady();
String message = ConfigCatLogMessages.getAutoPollMaxInitWaitTimeReached(autoPollingMode.getMaxInitWaitTimeSeconds());
this.logger.warn(4200, message);
Expand All @@ -75,8 +74,7 @@ public ConfigService(String sdkKey,
}

private void setInitialized() {
if (!initialized) {
initialized = true;
if (initialized.compareAndSet(false, true)) {
configCatHooks.invokeOnClientReady();
}
}
Expand Down Expand Up @@ -107,43 +105,34 @@ public CompletableFuture<SettingResult> getSettings() {
? new SettingResult(entryResult.value().getConfig().getEntries(), entryResult.value().getFetchTime())
: SettingResult.EMPTY);
} else {
return fetchIfOlder(Constants.DISTANT_PAST, true)
return fetchIfOlder(Constants.DISTANT_PAST, initialized.get()) // If we are initialized, we prefer the cached results
.thenApply(entryResult -> !entryResult.value().isEmpty()
? new SettingResult(entryResult.value().getConfig().getEntries(), entryResult.value().getFetchTime())
: SettingResult.EMPTY);
}

}

private CompletableFuture<Result<Entry>> fetchIfOlder(long time, boolean preferCached) {
private CompletableFuture<Result<Entry>> fetchIfOlder(long threshold, boolean preferCached) {
lock.lock();
try {
// Sync up with the cache and use it when it's not expired.
if (cachedEntry.isEmpty() || cachedEntry.getFetchTime() > time) {
Entry fromCache = readCache();
if (!fromCache.isEmpty() && !fromCache.getETag().equals(cachedEntry.getETag())) {
configCatHooks.invokeOnConfigChanged(fromCache.getConfig().getEntries());
cachedEntry = fromCache;
}
// Cache isn't expired
if (cachedEntry.getFetchTime() > time) {
setInitialized();
return CompletableFuture.completedFuture(Result.success(cachedEntry));
}
Entry fromCache = readCache();
if (!fromCache.isEmpty() && !fromCache.getETag().equals(cachedEntry.getETag())) {
configCatHooks.invokeOnConfigChanged(fromCache.getConfig().getEntries());
cachedEntry = fromCache;
}
// Use cache anyway (get calls on auto & manual poll must not initiate fetch).
// The initialized check ensures that we subscribe for the ongoing fetch during the
// max init wait time window in case of auto poll.
if (preferCached && initialized) {
// Cache isn't expired
if (cachedEntry.getFetchTime() > threshold) {
setInitialized();
return CompletableFuture.completedFuture(Result.success(cachedEntry));
}
// If we are in offline mode we are not allowed to initiate fetch.
if (offline.get()) {
// If we are in offline mode or the caller prefers cached values, do not initiate fetch.
if (offline.get() || preferCached) {
return CompletableFuture.completedFuture(Result.success(cachedEntry));
}

if (runningTask == null) {
// No fetch is running, initiate a new one.
if (runningTask == null) { // No fetch is running, initiate a new one.
runningTask = new CompletableFuture<>();
configFetcher.fetchAsync(cachedEntry.getETag())
.thenAccept(this::processResponse);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/configcat/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ private Constants() { /* prevent from instantiation*/ }
static final String CONFIG_JSON_NAME = "config_v5.json";
static final String SERIALIZATION_FORMAT_VERSION = "v2";

static final String VERSION = "8.3.0";
static final String VERSION = "8.4.0";
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;

public class AutoPollingPolicyTest {
public class AutoPollingTest {
private MockWebServer server;
private final ConfigCatLogger logger = new ConfigCatLogger(LoggerFactory.getLogger(AutoPollingPolicyTest.class), LogLevel.WARNING);
private final ConfigCatLogger logger = new ConfigCatLogger(LoggerFactory.getLogger(AutoPollingTest.class), LogLevel.WARNING);
private static final String TEST_JSON = "{ f: { fakeKey: { v: %s, p: [] ,r: [] } } }";

@BeforeEach
Expand Down
6 changes: 6 additions & 0 deletions src/test/java/com/configcat/Helpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ static String cacheValueFromConfigJson(String json) {
return entry.serialize();
}

static String cacheValueFromConfigJsonWithEtag(String json, String etag) {
Config config = Utils.gson.fromJson(json, Config.class);
Entry entry = new Entry(config, etag, json, System.currentTimeMillis());
return entry.serialize();
}

static void waitFor(Supplier<Boolean> predicate) throws InterruptedException {
waitFor(3000, predicate);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@

import static org.junit.jupiter.api.Assertions.*;

public class LazyLoadingPolicyTest {
public class LazyLoadingTest {
private ConfigService configService;
private MockWebServer server;
private final ConfigCatLogger logger = new ConfigCatLogger(LoggerFactory.getLogger(LazyLoadingPolicyTest.class));
private final ConfigCatLogger logger = new ConfigCatLogger(LoggerFactory.getLogger(LazyLoadingTest.class));
private static final String TEST_JSON = "{ f: { fakeKey: { v: %s, p: [] ,r: [] } } }";

@BeforeEach
Expand Down Expand Up @@ -146,6 +146,27 @@ void testCacheExpirationRespectedInTTLCalc304() throws InterruptedException, Exe
assertEquals(1, this.server.getRequestCount());
}

@Test
void testCacheTTLRespectsExternalCache() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(200).setBody(String.format(TEST_JSON, "test-remote")));

ConfigCache cache = new SingleValueCache(Helpers.cacheValueFromConfigJsonWithEtag(String.format(TEST_JSON, "test-local"), "etag"));

PollingMode mode = PollingModes
.lazyLoad(1);
ConfigFetcher fetcher = new ConfigFetcher(new OkHttpClient.Builder().build(), logger, "", this.server.url("/").toString(), false, mode.getPollingIdentifier());
ConfigService service = new ConfigService("", fetcher, mode, cache, logger, false, new ConfigCatHooks());

assertEquals("test-local", service.getSettings().get().settings().get("fakeKey").getValue().getAsString());
assertEquals(0, this.server.getRequestCount());
Thread.sleep(1000);

cache.write("", Helpers.cacheValueFromConfigJsonWithEtag(String.format(TEST_JSON, "test-local2"), "etag2"));
assertEquals("test-local2", service.getSettings().get().settings().get("fakeKey").getValue().getAsString());

assertEquals(0, this.server.getRequestCount());
}

@Test
void testOnlineOffline() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(200).setBody(String.format(TEST_JSON, "test")));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
import static org.junit.jupiter.api.Assertions.*;


public class ManualPollingPolicyTest {
public class ManualPollingTest {
private ConfigService configService;
private MockWebServer server;
private final ConfigCatLogger logger = new ConfigCatLogger(LoggerFactory.getLogger(ManualPollingPolicyTest.class));
private final ConfigCatLogger logger = new ConfigCatLogger(LoggerFactory.getLogger(ManualPollingTest.class));
private static final String TEST_JSON = "{ f: { fakeKey: { v: %s, p: [] ,r: [] } } }";

@BeforeEach
Expand Down

0 comments on commit da932c0

Please sign in to comment.