@@ -2,6 +2,7 @@ package dev.openfeature.sdk
22
33import dev.openfeature.sdk.events.OpenFeatureProviderEvents
44import dev.openfeature.sdk.exceptions.OpenFeatureError
5+ import kotlinx.coroutines.CancellationException
56import kotlinx.coroutines.CoroutineDispatcher
67import kotlinx.coroutines.CoroutineScope
78import kotlinx.coroutines.Dispatchers
@@ -18,7 +19,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
1819import kotlinx.coroutines.flow.filterIsInstance
1920import kotlinx.coroutines.flow.flatMapLatest
2021import kotlinx.coroutines.launch
21- import java.util.concurrent.CancellationException
2222
2323@Suppress(" TooManyFunctions" )
2424object OpenFeatureAPI {
@@ -42,7 +42,6 @@ object OpenFeatureAPI {
4242 * A flow of [OpenFeatureStatus] that emits the current status of the SDK.
4343 */
4444 val statusFlow: Flow <OpenFeatureStatus > get() = _statusFlow .distinctUntilChanged()
45- private var providerJob: Job ? = null
4645
4746 var hooks: List <Hook <* >> = listOf ()
4847 private set
@@ -64,7 +63,7 @@ object OpenFeatureAPI {
6463 dispatcher : CoroutineDispatcher = Dispatchers .IO ,
6564 initialContext : EvaluationContext ? = null
6665 ) {
67- setProviderJob?.cancel()
66+ setProviderJob?.cancel(CancellationException ( " Provider set job was cancelled due to new provider " ) )
6867 this .setProviderJob = CoroutineScope (SupervisorJob () + dispatcher).launch {
6968 setProviderInternal(provider, dispatcher, initialContext)
7069 }
@@ -85,8 +84,8 @@ object OpenFeatureAPI {
8584 }
8685
8786 private fun listenToProviderEvents (provider : FeatureProvider , dispatcher : CoroutineDispatcher ) {
88- providerJob ?.cancel()
89- this .providerJob = CoroutineScope (SupervisorJob () + dispatcher).launch {
87+ observeProviderEventsJob ?.cancel(CancellationException ( " Provider job was cancelled due to new provider " ) )
88+ this .observeProviderEventsJob = CoroutineScope (SupervisorJob () + dispatcher).launch {
9089 provider.observe().collect(handleProviderEvents)
9190 }
9291 }
@@ -105,20 +104,10 @@ object OpenFeatureAPI {
105104 }
106105 providersFlow.value = provider
107106 if (initialContext != null ) context = initialContext
108- try {
107+ tryWithStatusEmitErrorHandling {
109108 listenToProviderEvents(provider, dispatcher)
110109 getProvider().initialize(context)
111110 _statusFlow .emit(OpenFeatureStatus .Ready )
112- } catch (e: OpenFeatureError ) {
113- _statusFlow .emit(OpenFeatureStatus .Error (e))
114- } catch (e: Throwable ) {
115- _statusFlow .emit(
116- OpenFeatureStatus .Error (
117- OpenFeatureError .GeneralError (
118- e.message ? : e.javaClass.name
119- )
120- )
121- )
122111 }
123112 }
124113
@@ -171,7 +160,7 @@ object OpenFeatureAPI {
171160 evaluationContext : EvaluationContext ,
172161 dispatcher : CoroutineDispatcher = Dispatchers .IO
173162 ) {
174- setEvaluationContextJob?.cancel()
163+ setEvaluationContextJob?.cancel(CancellationException ( " Set context job was cancelled due to new context " ) )
175164 this .setEvaluationContextJob = CoroutineScope (SupervisorJob () + dispatcher).launch {
176165 setEvaluationContextInternal(evaluationContext)
177166 }
@@ -182,20 +171,28 @@ object OpenFeatureAPI {
182171 context = evaluationContext
183172 if (oldContext != evaluationContext) {
184173 _statusFlow .emit(OpenFeatureStatus .Reconciling )
185- try {
174+ tryWithStatusEmitErrorHandling {
186175 getProvider().onContextSet(oldContext, evaluationContext)
187176 _statusFlow .emit(OpenFeatureStatus .Ready )
188- } catch (e: OpenFeatureError ) {
189- _statusFlow .emit(OpenFeatureStatus .Error (e))
190- } catch (e: Throwable ) {
191- _statusFlow .emit(
192- OpenFeatureStatus .Error (
193- OpenFeatureError .GeneralError (
194- e.message ? : e.javaClass.name
195- )
177+ }
178+ }
179+ }
180+
181+ private suspend fun tryWithStatusEmitErrorHandling (function : suspend () -> Unit ) {
182+ try {
183+ function()
184+ } catch (e: CancellationException ) {
185+ // This happens by design and shouldn't be treated as an error
186+ } catch (e: OpenFeatureError ) {
187+ _statusFlow .emit(OpenFeatureStatus .Error (e))
188+ } catch (e: Throwable ) {
189+ _statusFlow .emit(
190+ OpenFeatureStatus .Error (
191+ OpenFeatureError .GeneralError (
192+ e.message ? : e.javaClass.name
196193 )
197194 )
198- }
195+ )
199196 }
200197 }
201198
@@ -242,9 +239,11 @@ object OpenFeatureAPI {
242239 */
243240 suspend fun shutdown () {
244241 clearHooks()
245- setEvaluationContextJob?.cancel(CancellationException (" Set context job was cancelled" ))
246- setProviderJob?.cancel(CancellationException (" Provider set job was cancelled" ))
247- observeProviderEventsJob?.cancel(CancellationException (" Provider event observe job was cancelled" ))
242+ setEvaluationContextJob?.cancel(CancellationException (" Set context job was cancelled due to shutdown" ))
243+ setProviderJob?.cancel(CancellationException (" Provider set job was cancelled due to shutdown" ))
244+ observeProviderEventsJob?.cancel(
245+ CancellationException (" Provider event observe job was cancelled due to shutdown" )
246+ )
248247 providerEventObservationScope?.coroutineContext?.cancelChildren()
249248 providerEventObservationScope?.coroutineContext?.cancel()
250249 clearProvider()
0 commit comments