-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Save cache file as received rather than serializing it again #51
Conversation
@@ -51,15 +50,15 @@ | |||
import cloud.eppo.android.dto.FlagConfig; | |||
import cloud.eppo.android.dto.RandomizationConfigResponse; | |||
import cloud.eppo.android.dto.SubjectAttributes; | |||
import cloud.eppo.android.dto.deserializers.EppoValueAdapter; | |||
import cloud.eppo.android.dto.adapters.EppoValueAdapter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that I understand more about Adapters, I renamed this package back to adapters
(how it was earlier)
|
||
public class EppoClientTest { | ||
private static final String TAG = logTag(EppoClient.class); | ||
private static final String DUMMY_API_KEY = "mock-api-key"; | ||
private static final String DUMMY_OTHER_API_KEY = "another-mock-api-key"; | ||
private static final String TEST_HOST = "https://us-central1-eppo-qa.cloudfunctions.net/serveGitHubRacTestFile"; | ||
private static final String INVALID_HOST = "https://thisisabaddomainforthistest.com"; | ||
private Gson gson = new GsonBuilder() | ||
private final Gson gson = new GsonBuilder() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 some cleanup suggested by Android Studio
@@ -266,7 +265,7 @@ public void testCachedAssignments() { | |||
// First initialize successfully | |||
initClient(TEST_HOST, true, true, false, DUMMY_API_KEY); // ensure cache is populated | |||
|
|||
// wait for a bit since cache file is loaded asynchronously | |||
// wait for a bit since cache file is written asynchronously |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really what we are waiting for
// We have two at-bats to load the configuration: loading from cache and fetching | ||
// The below variables help them keep track of each other's progress | ||
AtomicBoolean cacheLoadInProgress = new AtomicBoolean(true); | ||
AtomicReference<String> fetchErrorMessage = new AtomicReference<>(null); | ||
// We only want to fire the callback off once; so track whether or not we have yet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pulling back comments and tracking used in the UFC branch as the are more robust than what we have here.
public void setFlagsFromJsonString(String jsonString) { | ||
RandomizationConfigResponse config = gson.fromJson(jsonString, RandomizationConfigResponse.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This now takes and passes to Gson a string instead of a reader.
This should be fine, provided our responses are relatively (<100mb) small, which they will be.
} | ||
|
||
private void writeConfigToFile(RandomizationConfigResponse config) { | ||
public void asyncWriteToCache(String jsonString) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Promoted this to an upstream public function, as it seemed a weird side effect of a function that sets flags.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is a weird side effect. This is essentially a write through cache, which is a very common pattern
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah that's a good point; I'm keeping that pattern just separating the responsibilities of the underlying functions
@@ -60,7 +52,11 @@ public void get(String path, RequestCallback callback) { | |||
public void onResponse(Call call, Response response) { | |||
if (response.isSuccessful()) { | |||
Log.d(TAG, "Fetch successful"); | |||
callback.onSuccess(response.body().charStream()); | |||
try { | |||
callback.onSuccess(response.body().string()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using string()
to load the entire response into a string. This should be fine as long as its relatively small.
|
||
@Test | ||
public void testDeserializingDouble() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 These do not actually throw any (known) Exception
AsyncTask.execute(() -> { | ||
try { | ||
synchronized (cacheFile) { | ||
BufferedWriter writer = cacheFile.getWriter(); | ||
gson.toJson(config, writer); | ||
writer.write(jsonString); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👉 CORE FIX 👈
Write the response body instead of re-serializing
if (stringValue == "null") { | ||
return EppoValue.valueOf(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String equality uses .equals()
so this was never true and we don't even do this shenanigans in other SDKs / UFC so just axing it
cacheLoadInProgress.set(false); | ||
Log.d(TAG, "Did not initialize from cache"); | ||
// If cache loading failed, and fetching failed, fire off the failure callback if not yet done so | ||
// Otherwise, if fetching has not failed yet, defer to it for firing off callbacks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens is about the same time the fetching exceptional handler checks cacheLoadInProgress
, thinks it is in progress and returns? I guess this is covered right - we will eventually either get good data from the cache or empty data from the cache (if its empty). This is a good endpoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah so if cacheLoadInProgress
is checked right before it's set to false, then the fetcher won't fire off the callbacks but the cache load completion will because the final check for callback called flag is an atomic check and a set
Eppo Internal:
🎟️ ticket: FF-2159 - Android SDK Needs a custom serializer to handle code minification
If ProGuard is used, then Gson won't serialize flag configurations with known field names. To get around this, we should just write the JSON file exactly as we received it.
Verified fix checking the logs of our example app, which has its source minified using ProGuard.
Before:
After: