@@ -40,17 +40,29 @@ public class InProcessEvaluator implements IEvaluator {
4040 private final GoFeatureFlagProviderOptions options ;
4141 /** Method to call when we have a configuration change. */
4242 private final Consumer <ProviderEventDetails > emitProviderConfigurationChanged ;
43- /** Local copy of the flags' configuration. */
44- private volatile Map <String , Flag > flags ;
45- /** Evaluation context enrichment. */
46- private volatile Map <String , Object > evaluationContextEnrichment ;
47- /** Last hash of the flags' configuration. */
48- private volatile String etag ;
49- /** Last update of the flags' configuration. */
50- private volatile Date lastUpdate ;
43+ /** Immutable snapshot of all flag configuration state; updated atomically by the polling daemon. */
44+ private volatile EvaluatorState state ;
5145 /** disposable which manage the polling of the flag configurations. */
5246 private Disposable configurationDisposable ;
5347
48+ private static final class EvaluatorState {
49+ final Map <String , Flag > flags ;
50+ final Map <String , Object > evaluationContextEnrichment ;
51+ final String etag ;
52+ final Date lastUpdate ;
53+
54+ EvaluatorState (
55+ Map <String , Flag > flags ,
56+ Map <String , Object > evaluationContextEnrichment ,
57+ String etag ,
58+ Date lastUpdate ) {
59+ this .flags = flags ;
60+ this .evaluationContextEnrichment = evaluationContextEnrichment ;
61+ this .etag = etag ;
62+ this .lastUpdate = lastUpdate ;
63+ }
64+ }
65+
5466 /**
5567 * Constructor of the InProcessEvaluator.
5668 *
@@ -63,11 +75,9 @@ public InProcessEvaluator(
6375 GoFeatureFlagProviderOptions options ,
6476 Consumer <ProviderEventDetails > emitProviderConfigurationChanged ) {
6577 this .api = api ;
66- this .flags = Collections .emptyMap ();
67- this .etag = "" ;
6878 this .options = options ;
69- this .lastUpdate = new Date (0 );
7079 this .emitProviderConfigurationChanged = emitProviderConfigurationChanged ;
80+ this .state = new EvaluatorState (Collections .emptyMap (), null , "" , new Date (0 ));
7181 int poolSize = options .getWasmEvaluatorPoolSize () != null
7282 ? options .getWasmEvaluatorPoolSize ()
7383 : Const .DEFAULT_WASM_EVALUATOR_POOL_SIZE ;
@@ -76,8 +86,8 @@ public InProcessEvaluator(
7686
7787 @ Override
7888 public GoFeatureFlagResponse evaluate (String key , Object defaultValue , EvaluationContext evaluationContext ) {
79- Map < String , Flag > currentFlags = this .flags ;
80- if (currentFlags .get (key ) == null ) {
89+ EvaluatorState current = this .state ;
90+ if (current . flags .get (key ) == null ) {
8191 val err = new GoFeatureFlagResponse ();
8292 err .setReason (Reason .ERROR .name ());
8393 err .setErrorCode (ErrorCode .FLAG_NOT_FOUND .name ());
@@ -88,30 +98,29 @@ public GoFeatureFlagResponse evaluate(String key, Object defaultValue, Evaluatio
8898 val wasmInput = WasmInput .builder ()
8999 .flagContext (FlagContext .builder ()
90100 .defaultSdkValue (defaultValue )
91- .evaluationContextEnrichment (this .evaluationContextEnrichment )
101+ .evaluationContextEnrichment (current .evaluationContextEnrichment )
92102 .build ())
93103 .evalContext (evaluationContext .asObjectMap ())
94- .flag (currentFlags .get (key ))
104+ .flag (current . flags .get (key ))
95105 .flagKey (key )
96106 .build ();
97107 return this .evaluationPool .evaluate (wasmInput );
98108 }
99109
100110 @ Override
101111 public boolean isFlagTrackable (final String flagKey ) {
102- Flag flag = this .flags .get (flagKey );
112+ Flag flag = this .state . flags .get (flagKey );
103113 return flag != null && (flag .getTrackEvents () == null || flag .getTrackEvents ());
104114 }
105115
106116 @ Override
107117 public void init () {
108- val configFlags = api .retrieveFlagConfiguration (this .etag , options .getEvaluationFlagList ());
109- this .flags = configFlags .getFlags ();
110- this .etag = configFlags .getEtag ();
111- this .lastUpdate = configFlags .getLastUpdated ();
112- this .evaluationContextEnrichment = configFlags .getEvaluationContextEnrichment ();
113- // We call the WASM engine to avoid a cold start at the 1st evaluation
114- this .evaluationPool .preWarmWasm ();
118+ val configFlags = api .retrieveFlagConfiguration (this .state .etag , options .getEvaluationFlagList ());
119+ this .state = new EvaluatorState (
120+ configFlags .getFlags (),
121+ configFlags .getEvaluationContextEnrichment (),
122+ configFlags .getEtag (),
123+ configFlags .getLastUpdated ());
115124
116125 // start the polling of the flag configuration
117126 this .configurationDisposable = startCheckFlagConfigurationChangesDaemon ();
@@ -136,8 +145,8 @@ private Disposable startCheckFlagConfigurationChangesDaemon() {
136145
137146 Observable <Long > intervalObservable = Observable .interval (pollingIntervalMs , TimeUnit .MILLISECONDS );
138147 Observable <FlagConfigResponse > apiCallObservable = intervalObservable
139- .flatMap (tick -> Observable .fromCallable (
140- () -> this .api .retrieveFlagConfiguration (this .etag , options .getEvaluationFlagList ()))
148+ .flatMap (tick -> Observable .fromCallable (() ->
149+ this .api .retrieveFlagConfiguration (this . state .etag , options .getEvaluationFlagList ()))
141150 .onErrorResumeNext (e -> {
142151 log .error ("error while calling flag configuration API" , e );
143152 return Observable .empty ();
@@ -146,22 +155,24 @@ private Disposable startCheckFlagConfigurationChangesDaemon() {
146155
147156 return apiCallObservable .subscribe (
148157 response -> {
149- if (response .getEtag ().equals (this .etag )) {
158+ EvaluatorState current = this .state ;
159+ if (response .getEtag ().equals (current .etag )) {
150160 log .debug ("flag configuration has not changed: {}" , response );
151161 return ;
152162 }
153163
154- if (response .getLastUpdated ().before (this .lastUpdate )) {
164+ if (response .getLastUpdated ().before (current .lastUpdate )) {
155165 log .info ("configuration received is older than the current one" );
156166 return ;
157167 }
158168
159169 log .info ("flag configuration has changed" );
160- this .etag = response .getEtag ();
161- this .lastUpdate = response .getLastUpdated ();
162- val flagChanges = findFlagConfigurationChanges (this .flags , response .getFlags ());
163- this .flags = response .getFlags ();
164- this .evaluationContextEnrichment = response .getEvaluationContextEnrichment ();
170+ val flagChanges = findFlagConfigurationChanges (current .flags , response .getFlags ());
171+ this .state = new EvaluatorState (
172+ response .getFlags (),
173+ response .getEvaluationContextEnrichment (),
174+ response .getEtag (),
175+ response .getLastUpdated ());
165176 val changeDetails = ProviderEventDetails .builder ()
166177 .flagsChanged (flagChanges )
167178 .message ("flag configuration has changed" )
0 commit comments