Skip to content

Commit d79f27c

Browse files
authored
feat: intelligent enum for rule pre-compilation (#1791)
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
1 parent 9a1f89b commit d79f27c

9 files changed

Lines changed: 125 additions & 17 deletions

File tree

providers/flagd/README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,20 +160,27 @@ Given below are the supported configurations:
160160
| streamDeadlineMs | FLAGD_STREAM_DEADLINE_MS | int | 600000 | rpc & in-process |
161161
| keepAliveTime | FLAGD_KEEP_ALIVE_TIME_MS | long | 0 | rpc & in-process |
162162
| selector | FLAGD_SOURCE_SELECTOR | String | null | in-process (see [migration guidance](#selector-filtering-in-process-mode-only)) |
163-
| providerId | FLAGD_SOURCE_PROVIDER_ID | String | null | in-process |
163+
| providerId | FLAGD_PROVIDER_ID (FLAGD_SOURCE_PROVIDER_ID deprecated) | String | null | in-process |
164164
| cache | FLAGD_CACHE | String - lru, disabled | lru | rpc |
165165
| maxCacheSize | FLAGD_MAX_CACHE_SIZE | int | 1000 | rpc |
166-
| maxEventStreamRetries | FLAGD_MAX_EVENT_STREAM_RETRIES | int | 5 | rpc |
167-
| retryBackoffMs | FLAGD_RETRY_BACKOFF_MS | int | 1000 | rpc |
166+
| retryBackoffMs | FLAGD_RETRY_BACKOFF_MS | int | 1000 | rpc & in-process |
167+
| retryBackoffMaxMs | FLAGD_RETRY_BACKOFF_MAX_MS | int | 12000 | rpc & in-process |
168+
| retryGracePeriod | FLAGD_RETRY_GRACE_PERIOD | int | 5 | rpc & in-process & file |
169+
| fatalStatusCodes | FLAGD_FATAL_STATUS_CODES | list | [] | rpc & in-process |
168170
| offlineFlagSourcePath | FLAGD_OFFLINE_FLAG_SOURCE_PATH | String | null | file |
169171
| offlinePollIntervalMs | FLAGD_OFFLINE_POLL_MS | int | 5000 | file |
172+
| contextEnricher | - | function | identity | in-process |
173+
| compileTargeting | FLAGD_COMPILE_TARGETING | String - enabled, disabled, auto | auto | in-process (experimental) |
174+
| reinitializeOnError | FLAGD_REINITIALIZE_ON_ERROR | boolean | false | rpc & in-process (experimental) |
170175

171176
> [!NOTE]
172177
> Some configurations are only applicable for RPC resolver.
173178
174179
> [!NOTE]
175180
> The `selector` option automatically uses the `flagd-selector` header (the preferred approach per [flagd issue #1814](https://github.com/open-feature/flagd/issues/1814)) while maintaining backward compatibility with older flagd versions. See [Selector filtering](#selector-filtering-in-process-mode-only) for details.
176-
>
181+
182+
> [!TIP]
183+
> The `compileTargeting` option (experimental) controls pre-compilation of JsonLogic targeting rules into Java bytecode for improved evaluation performance. This requires the `jdk.compiler` module at runtime, which is typically not available in JRE-only images (distroless, Alpine, UBI Micro, etc.). In `auto` mode (the default), the provider calls `javax.tools.ToolProvider.getSystemJavaCompiler()` at initialization; if it returns non-null, compilation is enabled, otherwise the interpreter is used. Set to `enabled` to force compilation (a warning is logged if the compiler is unavailable), or `disabled` to always use the interpreter. Note that compilation adds latency to the first evaluation of each rule (source generation, in-memory javac, and class loading); subsequent evaluations of the same rule are faster.
177184
178185
### Unix socket support
179186

providers/flagd/pom.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,10 +239,6 @@
239239
<include>**/*CTest.java</include>
240240
</includes>
241241
<failIfNoTests>true</failIfNoTests>
242-
<environmentVariables>
243-
<!-- disable JsonLogic compilation to reduce VMLens interleaving combinations -->
244-
<FLAGD_DISABLE_TARGETING_COMPILATION>true</FLAGD_DISABLE_TARGETING_COMPILATION>
245-
</environmentVariables>
246242
</configuration>
247243
</execution>
248244
</executions>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package dev.openfeature.contrib.providers.flagd;
2+
3+
/**
4+
* !EXPERIMENTAL!
5+
* Controls whether JsonLogic targeting rules are compiled into Java bytecode for improved
6+
* evaluation performance. Compilation requires the {@code jdk.compiler} module at runtime,
7+
* which is typically not available in JRE-only images.
8+
* Compilation adds latency to the first evaluation of each rule (source generation, in-memory
9+
* javac, class loading); subsequent evaluations are faster. This trade-off favors long-running
10+
* services where rules are evaluated many times.
11+
*/
12+
public enum CompileTargetingMode {
13+
/**
14+
* Always attempt compilation. Logs a warning if the compiler is unavailable.
15+
*/
16+
ENABLED,
17+
18+
/**
19+
* Never compile; always use the interpreter.
20+
*/
21+
DISABLED,
22+
23+
/**
24+
* Auto-detect: checks {@code javax.tools.ToolProvider.getSystemJavaCompiler() != null} at
25+
* initialization. If the compiler is available, compilation is enabled; otherwise the
26+
* interpreter is used silently. This is the default.
27+
*/
28+
AUTO
29+
}

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/Config.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ public final class Config {
5757
static final String TARGET_URI_ENV_VAR_NAME = "FLAGD_TARGET_URI";
5858
static final String STREAM_RETRY_GRACE_PERIOD = "FLAGD_RETRY_GRACE_PERIOD";
5959
static final String REINITIALIZE_ON_ERROR_ENV_VAR_NAME = "FLAGD_REINITIALIZE_ON_ERROR";
60+
static final String COMPILE_TARGETING_ENV_VAR_NAME = "FLAGD_COMPILE_TARGETING";
61+
static final String DEFAULT_COMPILE_TARGETING = "auto";
6062

6163
static final String RESOLVER_RPC = "rpc";
6264
static final String RESOLVER_IN_PROCESS = "in-process";
@@ -128,6 +130,26 @@ static Resolver fromValueProvider(Function<String, String> provider) {
128130
}
129131
}
130132

133+
static CompileTargetingMode compileTargetingFromString(String value) {
134+
if (value == null) {
135+
return CompileTargetingMode.AUTO;
136+
}
137+
switch (value.toLowerCase()) {
138+
case "enabled":
139+
return CompileTargetingMode.ENABLED;
140+
case "disabled":
141+
return CompileTargetingMode.DISABLED;
142+
case "auto":
143+
return CompileTargetingMode.AUTO;
144+
default:
145+
log.warn(
146+
"Unrecognized FLAGD_COMPILE_TARGETING value: '{}'. "
147+
+ "Valid values are: enabled, disabled, auto. Defaulting to auto.",
148+
value);
149+
return CompileTargetingMode.AUTO;
150+
}
151+
}
152+
131153
/** intermediate interface to unify deprecated Evaluator and new Resolver. */
132154
public interface EvaluatorType {
133155
String asString();

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdOptions.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,23 @@ public class FlagdOptions {
236236
private boolean reinitializeOnError = Boolean.parseBoolean(
237237
fallBackToEnvOrDefault(Config.REINITIALIZE_ON_ERROR_ENV_VAR_NAME, Config.DEFAULT_REINITIALIZE_ON_ERROR));
238238

239+
/**
240+
* !EXPERIMENTAL!
241+
* Controls whether JsonLogic targeting rules are compiled into Java bytecode for improved
242+
* evaluation performance.
243+
* Requires the jdk.compiler module at runtime; falls back to interpreter mode if unavailable.
244+
* In AUTO mode, the provider checks {@code javax.tools.ToolProvider.getSystemJavaCompiler() != null}
245+
* at initialization to determine whether compilation is available.
246+
* Compilation adds latency to the first evaluation of each rule (source generation, in-memory
247+
* javac, class loading); subsequent evaluations are faster. This trade-off favors long-running
248+
* services where rules are evaluated many times.
249+
* Defaults to AUTO (compile if the compiler is available, otherwise use the interpreter).
250+
* Only applicable in the in-process mode.
251+
*/
252+
@Builder.Default
253+
private CompileTargetingMode compileTargeting = Config.compileTargetingFromString(
254+
fallBackToEnvOrDefault(Config.COMPILE_TARGETING_ENV_VAR_NAME, Config.DEFAULT_COMPILE_TARGETING));
255+
239256
/**
240257
* The evaluator to use for flag evaluations. Defaults to {@code new FlagdCore()}. Only applicable in the in-process
241258
* mode

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.openfeature.contrib.providers.flagd.resolver.process;
22

3+
import dev.openfeature.contrib.providers.flagd.CompileTargetingMode;
34
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
45
import dev.openfeature.contrib.providers.flagd.resolver.Resolver;
56
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.FlagStore;
@@ -20,6 +21,7 @@
2021
import dev.openfeature.sdk.internal.TriConsumer;
2122
import java.util.concurrent.atomic.AtomicBoolean;
2223
import java.util.concurrent.atomic.AtomicReference;
24+
import javax.tools.ToolProvider;
2325
import lombok.extern.slf4j.Slf4j;
2426

2527
/**
@@ -51,7 +53,8 @@ public InProcessResolver(
5153
FlagdOptions options, TriConsumer<ProviderEvent, ProviderEventDetails, Structure> onConnectionEvent) {
5254
Evaluator evaluator = options.getEvaluator();
5355
if (evaluator == null) {
54-
evaluator = new FlagdCore();
56+
boolean compile = resolveCompileTargeting(options.getCompileTargeting());
57+
evaluator = new FlagdCore(false, compile);
5558
}
5659
this.queueSource = getQueueSource(options);
5760
this.evaluator = evaluator;
@@ -190,4 +193,29 @@ static QueueSource getQueueSource(final FlagdOptions options) {
190193
? new FileQueueSource(options.getOfflineFlagSourcePath(), options.getOfflinePollIntervalMs())
191194
: new SyncStreamQueueSource(options);
192195
}
196+
197+
/**
198+
* Resolve the compile targeting mode to a boolean, performing auto-detection if needed.
199+
*/
200+
static boolean resolveCompileTargeting(CompileTargetingMode mode) {
201+
switch (mode) {
202+
case ENABLED:
203+
log.info("Targeting rule compilation enabled; requires the jdk.compiler module at runtime.");
204+
return true;
205+
case DISABLED:
206+
log.info("Targeting rule compilation is disabled.");
207+
return false;
208+
case AUTO:
209+
default:
210+
boolean compilerAvailable = ToolProvider.getSystemJavaCompiler() != null;
211+
if (compilerAvailable) {
212+
log.info("jdk.compiler detected; targeting rule compilation enabled automatically."
213+
+ " Set FLAGD_COMPILE_TARGETING=disabled to disable.");
214+
} else {
215+
log.info("jdk.compiler not detected; targeting rule compilation disabled."
216+
+ " Set FLAGD_COMPILE_TARGETING=enabled to force (requires a JDK at runtime).");
217+
}
218+
return compilerAvailable;
219+
}
220+
}
193221
}

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/FlagdOptionsTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ void testDefaults() {
5454
assertEquals(0, builder.getKeepAlive());
5555
assertNull(builder.getDefaultAuthority());
5656
assertNull(builder.getClientInterceptors());
57+
assertEquals(CompileTargetingMode.AUTO, builder.getCompileTargeting());
5758
}
5859

5960
@Test
@@ -78,6 +79,7 @@ void testBuilderOptions() {
7879
.keepAlive(1000)
7980
.defaultAuthority("test-authority.sync.example.com")
8081
.clientInterceptors(clientInterceptors)
82+
.compileTargeting(CompileTargetingMode.ENABLED)
8183
.build();
8284

8385
assertEquals("https://hosted-flagd", flagdOptions.getHost());
@@ -95,6 +97,7 @@ void testBuilderOptions() {
9597
assertEquals(1000, flagdOptions.getKeepAlive());
9698
assertEquals("test-authority.sync.example.com", flagdOptions.getDefaultAuthority());
9799
assertEquals(clientInterceptors, flagdOptions.getClientInterceptors());
100+
assertEquals(CompileTargetingMode.ENABLED, flagdOptions.getCompileTargeting());
98101
}
99102

100103
@Test

tools/flagd-core/src/main/java/dev/openfeature/contrib/tools/flagd/core/FlagdCore.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public class FlagdCore implements Evaluator {
6363
* Construct a FlagdCore instance.
6464
*/
6565
public FlagdCore() {
66-
this(false);
66+
this(false, false);
6767
}
6868

6969
/**
@@ -72,7 +72,17 @@ public FlagdCore() {
7272
* @param throwIfInvalid whether to throw an exception if flag configuration is invalid
7373
*/
7474
public FlagdCore(boolean throwIfInvalid) {
75-
this.operator = new Operator();
75+
this(throwIfInvalid, false);
76+
}
77+
78+
/**
79+
* Construct a FlagdCore instance.
80+
*
81+
* @param throwIfInvalid whether to throw an exception if flag configuration is invalid
82+
* @param compileTargeting whether to compile targeting rules for better performance
83+
*/
84+
public FlagdCore(boolean throwIfInvalid, boolean compileTargeting) {
85+
this.operator = new Operator(compileTargeting);
7686
this.throwIfInvalid = throwIfInvalid;
7787
}
7888

tools/flagd-core/src/main/java/dev/openfeature/contrib/tools/flagd/core/targeting/Operator.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,13 @@ public class Operator {
1919
static final String TARGET_KEY = "targetingKey";
2020
static final String TIME_STAMP = "timestamp";
2121

22-
static final String DISABLE_TARGETING_COMPILATION_ENV_VAR_NAME = "FLAGD_DISABLE_TARGETING_COMPILATION";
23-
2422
private final JsonLogic jsonLogicHandler;
2523

2624
/**
27-
* Construct a targeting operator.
28-
* Compiles targeting rules into native Java methods by default, unless the
29-
* FLAGD_DISABLE_TARGETING_COMPILATION environment variable is set.
25+
* Construct a targeting operator with compilation disabled.
3026
*/
3127
public Operator() {
32-
this(!Boolean.parseBoolean(System.getenv(DISABLE_TARGETING_COMPILATION_ENV_VAR_NAME)));
28+
this(false);
3329
}
3430

3531
/**

0 commit comments

Comments
 (0)