@@ -2,6 +2,7 @@ package dev.openfeature.sdk
2
2
3
3
import dev.openfeature.sdk.events.OpenFeatureProviderEvents
4
4
import dev.openfeature.sdk.exceptions.OpenFeatureError
5
+ import kotlinx.coroutines.CancellationException
5
6
import kotlinx.coroutines.CoroutineDispatcher
6
7
import kotlinx.coroutines.CoroutineScope
7
8
import kotlinx.coroutines.Dispatchers
@@ -18,7 +19,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
18
19
import kotlinx.coroutines.flow.filterIsInstance
19
20
import kotlinx.coroutines.flow.flatMapLatest
20
21
import kotlinx.coroutines.launch
21
- import java.util.concurrent.CancellationException
22
22
23
23
@Suppress(" TooManyFunctions" )
24
24
object OpenFeatureAPI {
@@ -42,7 +42,6 @@ object OpenFeatureAPI {
42
42
* A flow of [OpenFeatureStatus] that emits the current status of the SDK.
43
43
*/
44
44
val statusFlow: Flow <OpenFeatureStatus > get() = _statusFlow .distinctUntilChanged()
45
- private var providerJob: Job ? = null
46
45
47
46
var hooks: List <Hook <* >> = listOf ()
48
47
private set
@@ -64,7 +63,7 @@ object OpenFeatureAPI {
64
63
dispatcher : CoroutineDispatcher = Dispatchers .IO ,
65
64
initialContext : EvaluationContext ? = null
66
65
) {
67
- setProviderJob?.cancel()
66
+ setProviderJob?.cancel(CancellationException ( " Provider set job was cancelled due to new provider " ) )
68
67
this .setProviderJob = CoroutineScope (SupervisorJob () + dispatcher).launch {
69
68
setProviderInternal(provider, dispatcher, initialContext)
70
69
}
@@ -85,8 +84,8 @@ object OpenFeatureAPI {
85
84
}
86
85
87
86
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 {
90
89
provider.observe().collect(handleProviderEvents)
91
90
}
92
91
}
@@ -105,20 +104,10 @@ object OpenFeatureAPI {
105
104
}
106
105
providersFlow.value = provider
107
106
if (initialContext != null ) context = initialContext
108
- try {
107
+ tryWithStatusEmitErrorHandling {
109
108
listenToProviderEvents(provider, dispatcher)
110
109
getProvider().initialize(context)
111
110
_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
- )
122
111
}
123
112
}
124
113
@@ -171,7 +160,7 @@ object OpenFeatureAPI {
171
160
evaluationContext : EvaluationContext ,
172
161
dispatcher : CoroutineDispatcher = Dispatchers .IO
173
162
) {
174
- setEvaluationContextJob?.cancel()
163
+ setEvaluationContextJob?.cancel(CancellationException ( " Set context job was cancelled due to new context " ) )
175
164
this .setEvaluationContextJob = CoroutineScope (SupervisorJob () + dispatcher).launch {
176
165
setEvaluationContextInternal(evaluationContext)
177
166
}
@@ -182,20 +171,28 @@ object OpenFeatureAPI {
182
171
context = evaluationContext
183
172
if (oldContext != evaluationContext) {
184
173
_statusFlow .emit(OpenFeatureStatus .Reconciling )
185
- try {
174
+ tryWithStatusEmitErrorHandling {
186
175
getProvider().onContextSet(oldContext, evaluationContext)
187
176
_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
196
193
)
197
194
)
198
- }
195
+ )
199
196
}
200
197
}
201
198
@@ -242,9 +239,11 @@ object OpenFeatureAPI {
242
239
*/
243
240
suspend fun shutdown () {
244
241
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
+ )
248
247
providerEventObservationScope?.coroutineContext?.cancelChildren()
249
248
providerEventObservationScope?.coroutineContext?.cancel()
250
249
clearProvider()
0 commit comments