-
Notifications
You must be signed in to change notification settings - Fork 66
feat(flagd): Wasmanized flagd - this is just a rough PoC #1672
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
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Summary of ChangesHello @aepfli, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a significant architectural change by integrating a WebAssembly (WASM) based in-process resolver for flagd. This new resolver utilizes the flagd-evaluator WASM module to perform local flag evaluations, moving away from a purely Java-native approach. The changes encompass adding WASM runtime dependencies, implementing a dedicated WASM runtime manager with all required host functions, and a new resolver class to orchestrate the WASM module's lifecycle and interactions. This is presented as a Proof of Concept (PoC) to explore the benefits of WASM for flag evaluation. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
efc2975 to
3ccb8e0
Compare
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.
Code Review
This pull request introduces a new WASM-based in-process flag evaluation mechanism, replacing the existing InProcessResolver with InProcessWasmResolver. It adds the Chicory WASM runtime dependencies and a new FlagdWasmRuntime class responsible for loading the flagd-evaluator.wasm module and providing 9 essential host functions, including critical ones for time and random value generation, legacy timestamp functions, error handling, and wasm-bindgen no-ops. A new FlagsChangedResponse class is added for handling flag update payloads from the WASM module. The InProcessWasmResolver manages WASM memory, allocates space for flag keys and evaluation contexts, calls WASM export functions for evaluation and state updates, and processes flag changes in a dedicated stateWatcher thread. Review comments highlight several issues: the WASM module loading is brittle and should use classpath resources; the resolve method in InProcessWasmResolver is not thread-safe and needs synchronization; error handling in the stateWatcher thread is too aggressive, leading to termination; the ImmutableMetadataDeserializer incorrectly converts all metadata values to strings, losing type information; the init() method of InProcessWasmResolver is not called in tests, preventing flag loading; the SecureRandom instance in FlagdWasmRuntime should be static for efficiency; the shutdown() method in InProcessWasmResolver is unimplemented; and there is a significant amount of unused host function implementations and metadata helper methods in InProcessWasmResolver that should be removed.
I am having trouble creating individual review comments. Click here to see my feedback.
providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/FlagdWasmRuntime.java (35-39)
Loading the WASM module using Path.of("flagd_evaluator.wasm") is brittle as it relies on the file being present in the current working directory. This will likely fail in many deployment scenarios. The WASM file should be bundled as a resource within the JAR and loaded from the classpath.
try (java.io.InputStream wasmStream = FlagdWasmRuntime.class.getClassLoader().getResourceAsStream("flagd_evaluator.wasm")) {
if (wasmStream == null) {
throw new java.io.IOException("flagd_evaluator.wasm not found in classpath");
}
wasmBytes = wasmStream.readAllBytes();
} catch (IOException e) {
throw new RuntimeException("Failed to load flagd_evaluator.wasm", e);
}providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessWasmResolver.java (226)
The resolve method is not thread-safe. A single WASM Instance is shared across all evaluations. If multiple threads call evaluation methods concurrently, they will execute resolve and perform memory operations (alloc, write, evaluate, dealloc) on the same WASM memory, leading to race conditions and incorrect results. The entire resolve method should be synchronized to ensure that evaluations are executed serially.
private synchronized <T> ProviderEvaluation<T> resolve(Class<T> type, String key, EvaluationContext ctx) {providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessWasmResolver.java (153-157)
Catching JsonMappingException and JsonProcessingException and re-throwing them as RuntimeException will cause the stateWatcher thread to terminate. This means that no further flag configuration updates will be processed. These exceptions should be logged as errors, and the loop should continue to ensure the resolver can recover from malformed data and process subsequent valid updates.
} catch (JsonMappingException | JsonProcessingException e) {
log.error("Failed to process flag update payload", e);
}providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessWasmResolver.java (543-545)
The custom ImmutableMetadataDeserializer incorrectly converts all metadata values to strings using entry.getValue().toString(). This causes a loss of type information (e.g., numbers and booleans become strings), which can lead to unexpected behavior in hooks or client applications that inspect the flag metadata. The deserializer should preserve the original types of the values.
for (Map.Entry<String, Object> entry : map.entrySet()) {
final String key = entry.getKey();
final Object value = entry.getValue();
if (value instanceof Boolean) {
builder.addBoolean(key, (Boolean) value);
} else if (value instanceof String) {
builder.addString(key, (String) value);
} else if (value instanceof Integer) {
builder.addInteger(key, (Integer) value);
} else if (value instanceof Double) {
builder.addDouble(key, (Double) value);
} else if (value != null) {
builder.addString(key, value.toString());
}
}providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/WasmResolverTest.java (16-22)
The InProcessWasmResolver is instantiated in a static block, but its init() method is never called. The init() method is responsible for starting the FileQueueSource poller, which loads the flag configurations from test.json. Without calling init(), the resolver will not have any flags loaded, and the evaluations in testWasmResolverInit will likely fail with FLAG_NOT_FOUND errors. The init() method should be called after the resolver is created, and the operation should be wrapped in a try-catch block as init() can throw a checked exception.
static {
try {
System.out.println("Creating InProcessWasmResolver...");
FlagdOptions options =
FlagdOptions.builder().offlineFlagSourcePath("test.json").build();
RESOLVER = new InProcessWasmResolver(options, event -> {});
RESOLVER.init();
System.out.println("InProcessWasmResolver created successfully!");
} catch (Exception e) {
throw new RuntimeException("Failed to initialize WasmResolverTest", e);
}
}providers/flagd/WASM_HOST_IMPORTS.md (96)
This documentation shows creating a new SecureRandom instance on each call, which is inefficient. I've left a comment on FlagdWasmRuntime.java to suggest using a static SecureRandom instance. If you apply that change, please update this documentation to reflect the improved implementation (e.g., by adding a comment about the static SECURE_RANDOM field and using it here).
SECURE_RANDOM.nextBytes(randomBytes);
providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessWasmResolver.java (168)
The shutdown() method is currently empty. For a clean shutdown, it should stop the stateWatcher thread and shut down the connector.
To do this, you'll need to:
- Promote the
stateWatcherlocal variable ininit()to a class field:private Thread stateWatcher; - In
init(), assign to this field:this.stateWatcher = new Thread(...). - Implement the
shutdown()method as suggested to interrupt the thread and close the connector.
public void shutdown() throws InterruptedException {
if (stateWatcher != null) {
stateWatcher.interrupt();
}
connector.shutdown();
}providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessWasmResolver.java (264-532)
This file contains a significant amount of unused code. The methods getFlagMetadata, addEntryToMetadataBuilder, and all the create... methods for host functions (from createObjectDropRef to createWbindgenError) appear to be dead code, likely leftovers from a previous implementation or experimentation. The required host functions are already correctly defined and provided by FlagdWasmRuntime. Removing this unused code will improve maintainability.
This PR
Related Issues
Fixes #1234523
Notes
Follow-up Tasks
How to test