@@ -36,15 +36,12 @@ public class FlagdProvider extends EventProvider {
3636 private Function <Structure , EvaluationContext > contextEnricher ;
3737 private static final String FLAGD_PROVIDER = "flagd" ;
3838 private final Resolver flagResolver ;
39- private volatile boolean initialized = false ;
40- private volatile Structure syncMetadata = new ImmutableStructure ();
41- private volatile EvaluationContext enrichedContext = new ImmutableContext ();
4239 private final List <Hook > hooks = new ArrayList <>();
43- private volatile ProviderEvent previousEvent = null ;
44- private final Object eventLock ;
40+ private final EventsLock eventsLock = new EventsLock ();
4541
4642 /**
47- * An executor service responsible for emitting {@link ProviderEvent#PROVIDER_ERROR} after the provider went
43+ * An executor service responsible for emitting
44+ * {@link ProviderEvent#PROVIDER_ERROR} after the provider went
4845 * {@link ProviderEvent#PROVIDER_STALE} for {@link #gracePeriod} seconds.
4946 */
5047 private final ScheduledExecutorService errorExecutor ;
@@ -55,7 +52,8 @@ public class FlagdProvider extends EventProvider {
5552 private ScheduledFuture <?> errorTask ;
5653
5754 /**
58- * The grace period in milliseconds to wait after {@link ProviderEvent#PROVIDER_STALE} before emitting a
55+ * The grace period in milliseconds to wait after
56+ * {@link ProviderEvent#PROVIDER_STALE} before emitting a
5957 * {@link ProviderEvent#PROVIDER_ERROR}.
6058 */
6159 private final long gracePeriod ;
@@ -96,10 +94,22 @@ public FlagdProvider(final FlagdOptions options) {
9694 }
9795 hooks .add (new SyncMetadataHook (this ::getEnrichedContext ));
9896 contextEnricher = options .getContextEnricher ();
99- this .errorExecutor = Executors .newSingleThreadScheduledExecutor ();
100- this .gracePeriod = options .getRetryGracePeriod ();
101- this .deadline = options .getDeadline ();
102- this .eventLock = new Object ();
97+ errorExecutor = Executors .newSingleThreadScheduledExecutor ();
98+ gracePeriod = options .getRetryGracePeriod ();
99+ deadline = options .getDeadline ();
100+ }
101+
102+ /**
103+ * Internal constructor for test cases.
104+ * DO NOT MAKE PUBLIC
105+ */
106+ FlagdProvider (Resolver resolver , boolean initialized ) {
107+ this .flagResolver = resolver ;
108+ deadline = Config .DEFAULT_DEADLINE ;
109+ gracePeriod = Config .DEFAULT_STREAM_RETRY_GRACE_PERIOD ;
110+ hooks .add (new SyncMetadataHook (this ::getEnrichedContext ));
111+ errorExecutor = Executors .newSingleThreadScheduledExecutor ();
112+ this .eventsLock .initialized = initialized ;
103113 }
104114
105115 @ Override
@@ -108,21 +118,26 @@ public List<Hook> getProviderHooks() {
108118 }
109119
110120 @ Override
111- public synchronized void initialize (EvaluationContext evaluationContext ) throws Exception {
112- if (this .initialized ) {
113- return ;
114- }
121+ public void initialize (EvaluationContext evaluationContext ) throws Exception {
122+ synchronized (eventsLock ) {
123+ if (eventsLock .initialized ) {
124+ return ;
125+ }
115126
116- this .flagResolver .init ();
117- // block till ready - this works with deadline fine for rpc, but with in_process we also need to take parsing
118- // into the equation
119- // TODO: evaluate where we are losing time, so we can remove this magic number - follow up
120- Util .busyWaitAndCheck (this .deadline + 200 , () -> initialized );
127+ flagResolver .init ();
128+ }
129+ // block till ready - this works with deadline fine for rpc, but with in_process
130+ // we also need to take parsing into the equation
131+ // TODO: evaluate where we are losing time, so we can remove this magic number -
132+ // follow up
133+ // wait outside of the synchonrization or we'll deadlock
134+ Util .busyWaitAndCheck (this .deadline * 2 , () -> eventsLock .initialized );
121135 }
122136
123137 @ Override
124- public synchronized void shutdown () {
125- if (!this .initialized ) {
138+ public void shutdown () {
139+ synchronized (eventsLock ) {
140+ if (!eventsLock .initialized ) {
126141 return ;
127142 }
128143 try {
@@ -131,10 +146,11 @@ public synchronized void shutdown() {
131146 errorExecutor .shutdownNow ();
132147 errorExecutor .awaitTermination (deadline , TimeUnit .MILLISECONDS );
133148 }
134- } catch (Exception e ) {
135- log .error ("Error during shutdown {}" , FLAGD_PROVIDER , e );
136- } finally {
137- this .initialized = false ;
149+ } catch (Exception e ) {
150+ log .error ("Error during shutdown {}" , FLAGD_PROVIDER , e );
151+ } finally {
152+ eventsLock .initialized = false ;
153+ }
138154 }
139155 }
140156
@@ -145,27 +161,27 @@ public Metadata getMetadata() {
145161
146162 @ Override
147163 public ProviderEvaluation <Boolean > getBooleanEvaluation (String key , Boolean defaultValue , EvaluationContext ctx ) {
148- return this . flagResolver .booleanEvaluation (key , defaultValue , ctx );
164+ return flagResolver .booleanEvaluation (key , defaultValue , ctx );
149165 }
150166
151167 @ Override
152168 public ProviderEvaluation <String > getStringEvaluation (String key , String defaultValue , EvaluationContext ctx ) {
153- return this . flagResolver .stringEvaluation (key , defaultValue , ctx );
169+ return flagResolver .stringEvaluation (key , defaultValue , ctx );
154170 }
155171
156172 @ Override
157173 public ProviderEvaluation <Double > getDoubleEvaluation (String key , Double defaultValue , EvaluationContext ctx ) {
158- return this . flagResolver .doubleEvaluation (key , defaultValue , ctx );
174+ return flagResolver .doubleEvaluation (key , defaultValue , ctx );
159175 }
160176
161177 @ Override
162178 public ProviderEvaluation <Integer > getIntegerEvaluation (String key , Integer defaultValue , EvaluationContext ctx ) {
163- return this . flagResolver .integerEvaluation (key , defaultValue , ctx );
179+ return flagResolver .integerEvaluation (key , defaultValue , ctx );
164180 }
165181
166182 @ Override
167183 public ProviderEvaluation <Value > getObjectEvaluation (String key , Value defaultValue , EvaluationContext ctx ) {
168- return this . flagResolver .objectEvaluation (key , defaultValue , ctx );
184+ return flagResolver .objectEvaluation (key , defaultValue , ctx );
169185 }
170186
171187 /**
@@ -178,7 +194,7 @@ public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultVa
178194 * @return Object map representing sync metadata
179195 */
180196 protected Structure getSyncMetadata () {
181- return new ImmutableStructure (syncMetadata .asMap ());
197+ return new ImmutableStructure (eventsLock . syncMetadata .asMap ());
182198 }
183199
184200 /**
@@ -187,42 +203,45 @@ protected Structure getSyncMetadata() {
187203 * @return context
188204 */
189205 EvaluationContext getEnrichedContext () {
190- return enrichedContext ;
206+ return eventsLock . enrichedContext ;
191207 }
192208
193209 @ SuppressWarnings ("checkstyle:fallthrough" )
194210 private void onProviderEvent (FlagdProviderEvent flagdProviderEvent ) {
195211
196- synchronized (eventLock ) {
212+ synchronized (eventsLock ) {
197213 log .info ("FlagdProviderEvent: {}" , flagdProviderEvent );
198- syncMetadata = flagdProviderEvent .getSyncMetadata ();
214+ eventsLock . syncMetadata = flagdProviderEvent .getSyncMetadata ();
199215 if (flagdProviderEvent .getSyncMetadata () != null ) {
200- enrichedContext = contextEnricher .apply (flagdProviderEvent .getSyncMetadata ());
216+ eventsLock . enrichedContext = contextEnricher .apply (flagdProviderEvent .getSyncMetadata ());
201217 }
202218
203219 /*
204- We only use Error and Ready as previous states.
205- As error will first be emitted as Stale, and only turns after a while into an emitted Error.
206- Ready is needed, as the InProcessResolver does not have a dedicated ready event, hence we need to
207- forward a configuration changed to the ready, if we are not in the ready state.
220+ * We only use Error and Ready as previous states.
221+ * As error will first be emitted as Stale, and only turns after a while into an
222+ * emitted Error.
223+ * Ready is needed, as the InProcessResolver does not have a dedicated ready
224+ * event, hence we need to
225+ * forward a configuration changed to the ready, if we are not in the ready
226+ * state.
208227 */
209228 switch (flagdProviderEvent .getEvent ()) {
210229 case PROVIDER_CONFIGURATION_CHANGED :
211- if (previousEvent == ProviderEvent .PROVIDER_READY ) {
230+ if (eventsLock . previousEvent == ProviderEvent .PROVIDER_READY ) {
212231 onConfigurationChanged (flagdProviderEvent );
213232 break ;
214233 }
215234 // intentional fall through, a not-ready change will trigger a ready.
216235 case PROVIDER_READY :
217236 onReady ();
218- previousEvent = ProviderEvent .PROVIDER_READY ;
237+ eventsLock . previousEvent = ProviderEvent .PROVIDER_READY ;
219238 break ;
220239
221240 case PROVIDER_ERROR :
222- if (previousEvent != ProviderEvent .PROVIDER_ERROR ) {
241+ if (eventsLock . previousEvent != ProviderEvent .PROVIDER_ERROR ) {
223242 onError ();
224243 }
225- previousEvent = ProviderEvent .PROVIDER_ERROR ;
244+ eventsLock . previousEvent = ProviderEvent .PROVIDER_ERROR ;
226245 break ;
227246 default :
228247 log .info ("Unknown event {}" , flagdProviderEvent .getEvent ());
@@ -238,8 +257,8 @@ private void onConfigurationChanged(FlagdProviderEvent flagdProviderEvent) {
238257 }
239258
240259 private void onReady () {
241- if (!initialized ) {
242- initialized = true ;
260+ if (!eventsLock . initialized ) {
261+ eventsLock . initialized = true ;
243262 log .info ("initialized FlagdProvider" );
244263 }
245264 if (errorTask != null && !errorTask .isCancelled ()) {
@@ -264,7 +283,7 @@ private void onError() {
264283 if (!errorExecutor .isShutdown ()) {
265284 errorTask = errorExecutor .schedule (
266285 () -> {
267- if (previousEvent == ProviderEvent .PROVIDER_ERROR ) {
286+ if (eventsLock . previousEvent == ProviderEvent .PROVIDER_ERROR ) {
268287 log .debug (
269288 "Provider did not reconnect successfully within {}s. Emit ERROR event..." ,
270289 gracePeriod );
@@ -278,4 +297,15 @@ private void onError() {
278297 TimeUnit .SECONDS );
279298 }
280299 }
300+
301+ /**
302+ * Contains all fields we need to worry about locking, used as intrinsic lock
303+ * for sync blocks.
304+ */
305+ static class EventsLock {
306+ volatile ProviderEvent previousEvent = null ;
307+ volatile Structure syncMetadata = new ImmutableStructure ();
308+ volatile boolean initialized = false ;
309+ volatile EvaluationContext enrichedContext = new ImmutableContext ();
310+ }
281311}
0 commit comments