Skip to content

Commit 123b9be

Browse files
Merge pull request #80 from EtaCassiopeia/fix/async-init-timing
Fix async init timing for slow CI runners
2 parents 14cb214 + e3271ff commit 123b9be

3 files changed

Lines changed: 24 additions & 10 deletions

File tree

core/src/main/scala/zio/openfeature/FeatureFlags.scala

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,8 @@ object FeatureFlags {
484484
initialHooks: List[FeatureHook],
485485
statusRef: Option[Ref[ProviderStatus]],
486486
addShutdownFinalizer: Boolean,
487-
apiOverride: Option[OpenFeatureAPI] = None
487+
apiOverride: Option[OpenFeatureAPI] = None,
488+
onReady: Option[java.util.concurrent.CountDownLatch] = None
488489
): ZIO[Scope, Throwable, FeatureFlagsLive] =
489490
for {
490491
api <- ZIO.succeed(apiOverride.getOrElse(OpenFeatureAPI.getInstance()))
@@ -502,7 +503,7 @@ object FeatureFlags {
502503
state = statusRef.fold(baseState)(ref => baseState.copy(statusRef = ref))
503504
_ <- state.hooksRef.set(initialHooks)
504505
_ <- ZIO.when(addShutdownFinalizer)(ZIO.addFinalizer(ZIO.attemptBlocking(api.shutdown()).ignore))
505-
ff = new FeatureFlagsLive(client, provider, providerName, domain, state, api)
506+
ff = new FeatureFlagsLive(client, provider, providerName, domain, state, api, onReady)
506507
// Start event bridge — if provider is already ready, replay fires immediately
507508
_ <- ff.startEventBridge
508509
} yield ff
@@ -531,7 +532,8 @@ object FeatureFlags {
531532
provider: OFFeatureProvider,
532533
domain: String,
533534
statusRef: Ref[ProviderStatus],
534-
api: Option[OpenFeatureAPI] = None
535+
api: Option[OpenFeatureAPI] = None,
536+
onReady: Option[java.util.concurrent.CountDownLatch] = None
535537
): ZLayer[Scope, Throwable, FeatureFlags] =
536538
ZLayer.scoped(
537539
buildAsync(
@@ -540,7 +542,8 @@ object FeatureFlags {
540542
initialHooks = Nil,
541543
statusRef = Some(statusRef),
542544
addShutdownFinalizer = false,
543-
apiOverride = api
545+
apiOverride = api,
546+
onReady = onReady
544547
)
545548
)
546549

core/src/main/scala/zio/openfeature/FeatureFlagsLive.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ final private[openfeature] class FeatureFlagsLive(
2424
providerName: String,
2525
domain: Option[String],
2626
state: FeatureFlagsState,
27-
api: OpenFeatureAPI
27+
api: OpenFeatureAPI,
28+
onReady: Option[java.util.concurrent.CountDownLatch] = None
2829
) extends FeatureFlags {
2930

3031
// Bridge Java SDK provider events to ZIO event system
@@ -48,6 +49,7 @@ final private[openfeature] class FeatureFlagsLive(
4849
state.eventHub.publish(ProviderEvent.Ready(metadata, em))
4950
)
5051
.getOrThrowFiberFailure()
52+
onReady.foreach(_.countDown())
5153
}
5254

5355
val errorHandler: java.util.function.Consumer[EventDetails] = details =>

testkit/src/main/scala/zio/openfeature/testkit/TestFeatureProvider.scala

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ final class TestFeatureProvider private (
3535
private val eventsHubRef: Ref[Hub[ProviderEvent]],
3636
private[openfeature] val statusRef: Ref[ProviderStatus],
3737
private val initLatch: Option[CountDownLatch],
38-
private val initDone: Option[CountDownLatch]
38+
private[testkit] val initDone: Option[CountDownLatch]
3939
) extends EventProvider {
4040

4141
@scala.annotation.nowarn("msg=deprecated")
@@ -51,7 +51,9 @@ final class TestFeatureProvider private (
5151
case None => ()
5252
}
5353
state.set(ProviderState.READY)
54-
initDone.foreach(_.countDown()) // Signal that initialize() has completed
54+
// Note: initDone is NOT counted down here. It is counted down by the event bridge's
55+
// readyHandler when the Java SDK fires PROVIDER_READY, ensuring the SDK has fully
56+
// processed the initialization before setStatus(Ready) returns.
5557
}
5658

5759
override def shutdown(): Unit = {
@@ -196,8 +198,9 @@ final class TestFeatureProvider private (
196198
case ProviderStatus.Ready =>
197199
state.set(ProviderState.READY)
198200
initLatch.foreach(_.countDown())
199-
// Wait for the Java SDK's background initialize() thread to complete, ensuring
200-
// the provider is fully registered before evaluations are attempted.
201+
// Wait for the event bridge to receive PROVIDER_READY from the Java SDK.
202+
// This confirms the SDK has fully processed the initialization and the
203+
// provider is registered as ready — no sleep needed.
201204
initDone.foreach(_.await())
202205
case ProviderStatus.NotReady => state.set(ProviderState.NOT_READY)
203206
case ProviderStatus.Error => state.set(ProviderState.ERROR)
@@ -380,7 +383,13 @@ object TestFeatureProvider {
380383
api = OpenFeatureAPIFactory.create()
381384
domain = s"test-async-${java.util.UUID.randomUUID()}"
382385
featureFlags <- FeatureFlags
383-
.fromProviderWithDomainAsync(testProvider, domain, testProvider.statusRef, api = Some(api))
386+
.fromProviderWithDomainAsync(
387+
testProvider,
388+
domain,
389+
testProvider.statusRef,
390+
api = Some(api),
391+
onReady = testProvider.initDone
392+
)
384393
.build
385394
.map(_.get)
386395
} yield (testProvider, featureFlags)

0 commit comments

Comments
 (0)