You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: internal provider state, new client events (#241)
* moves provider state into SDK (SDK now maintains provider
state/lifecycle; provider interface is now "stateless")
* refine semantics around client state when context is
pending/reconciled by using new events/states, instead of STALE
* add PROVIDER_FATAL error code
* add PROVIDER_RECONCILING event (client only)
* add RECONCILING provider status status (client only)
Resolves: #238
---------
Signed-off-by: Todd Baert <[email protected]>
Co-authored-by: Michael Beemer <[email protected]>
Co-authored-by: Lukas Reining <[email protected]>
Co-authored-by: Kavindu Dodanduwa <[email protected]>
Co-authored-by: Fabrizio Demaria <[email protected]>
Copy file name to clipboardExpand all lines: specification.json
+98-33
Original file line number
Diff line number
Diff line change
@@ -287,6 +287,70 @@
287
287
"RFC 2119 keyword": "MUST",
288
288
"children": []
289
289
},
290
+
{
291
+
"id": "Requirement 1.7.1",
292
+
"machine_id": "requirement_1_7_1",
293
+
"content": "The `client` MUST define a `provider status` accessor which indicates the readiness of the associated provider, with possible values `NOT_READY`, `READY`, `STALE`, `ERROR`, or `FATAL`.",
294
+
"RFC 2119 keyword": "MUST",
295
+
"children": []
296
+
},
297
+
{
298
+
"id": "Condition 1.7.2",
299
+
"machine_id": "condition_1_7_2",
300
+
"content": "The implementation uses the static-context paradigm.",
301
+
"RFC 2119 keyword": null,
302
+
"children": [
303
+
{
304
+
"id": "Conditional Requirement 1.7.2.1",
305
+
"machine_id": "conditional_requirement_1_7_2_1",
306
+
"content": "In addition to `NOT_READY`, `READY`, `STALE`, or `ERROR`, the `provider status` accessor must support possible value `RECONCILING`.",
307
+
"RFC 2119 keyword": null,
308
+
"children": []
309
+
}
310
+
]
311
+
},
312
+
{
313
+
"id": "Requirement 1.7.3",
314
+
"machine_id": "requirement_1_7_3",
315
+
"content": "The client's `provider status` accessor MUST indicate `READY` if the `initialize` function of the associated provider terminates normally.",
316
+
"RFC 2119 keyword": "MUST",
317
+
"children": []
318
+
},
319
+
{
320
+
"id": "Requirement 1.7.4",
321
+
"machine_id": "requirement_1_7_4",
322
+
"content": "The client's `provider status` accessor MUST indicate `ERROR` if the `initialize` function of the associated provider terminates abnormally.",
323
+
"RFC 2119 keyword": "MUST",
324
+
"children": []
325
+
},
326
+
{
327
+
"id": "Requirement 1.7.5",
328
+
"machine_id": "requirement_1_7_5",
329
+
"content": "The client's `provider status` accessor MUST indicate `FATAL` if the `initialize` function of the associated provider terminates abnormally and indicates `error code` `PROVIDER_FATAL`.",
330
+
"RFC 2119 keyword": "MUST",
331
+
"children": []
332
+
},
333
+
{
334
+
"id": "Requirement 1.7.6",
335
+
"machine_id": "requirement_1_7_6",
336
+
"content": "The client MUST default, run error hooks, and indicate an error if flag resolution is attempted while the provider is in `NOT_READY`.",
337
+
"RFC 2119 keyword": "MUST",
338
+
"children": []
339
+
},
340
+
{
341
+
"id": "Requirement 1.7.7",
342
+
"machine_id": "requirement_1_7_7",
343
+
"content": "The client MUST default, run error hooks, and indicate an error if flag resolution is attempted while the provider is in `PROVIDER_FATAL`.",
344
+
"RFC 2119 keyword": "MUST",
345
+
"children": []
346
+
},
347
+
{
348
+
"id": "Requirement 1.7.8",
349
+
"machine_id": "requirement_1_7_8",
350
+
"content": "Implementations SHOULD propagate the `error code` returned from any provider lifecycle methods.",
351
+
"RFC 2119 keyword": "SHOULD",
352
+
"children": []
353
+
},
290
354
{
291
355
"id": "Requirement 2.1.1",
292
356
"machine_id": "requirement_2_1_1",
@@ -409,32 +473,19 @@
409
473
"children": []
410
474
},
411
475
{
412
-
"id": "Requirement 2.4.2",
413
-
"machine_id": "requirement_2_4_2",
414
-
"content": "The `provider` MAY define a `status` field/accessor which indicates the readiness of the provider, with possible values `NOT_READY`, `READY`, `STALE`, or `ERROR`.",
415
-
"RFC 2119 keyword": "MAY",
416
-
"children": []
417
-
},
418
-
{
419
-
"id": "Requirement 2.4.3",
420
-
"machine_id": "requirement_2_4_3",
421
-
"content": "The provider MUST set its `status` field/accessor to `READY` if its `initialize` function terminates normally.",
422
-
"RFC 2119 keyword": "MUST",
423
-
"children": []
424
-
},
425
-
{
426
-
"id": "Requirement 2.4.4",
427
-
"machine_id": "requirement_2_4_4",
428
-
"content": "The provider MUST set its `status` field to `ERROR` if its `initialize` function terminates abnormally.",
429
-
"RFC 2119 keyword": "MUST",
430
-
"children": []
431
-
},
432
-
{
433
-
"id": "Requirement 2.4.5",
434
-
"machine_id": "requirement_2_4_5",
435
-
"content": "The provider SHOULD indicate an error if flag resolution is attempted before the provider is ready.",
436
-
"RFC 2119 keyword": "SHOULD",
437
-
"children": []
476
+
"id": "Condition 2.4.2",
477
+
"machine_id": "condition_2_4_2",
478
+
"content": "The provider defines an `initialize` function.",
479
+
"RFC 2119 keyword": null,
480
+
"children": [
481
+
{
482
+
"id": "Conditional Requirement 2.4.2.1",
483
+
"machine_id": "conditional_requirement_2_4_2_1",
484
+
"content": "If the provider's `initialize` function fails to render the provider ready to evaluate flags, it SHOULD abnormally terminate.",
485
+
"RFC 2119 keyword": "SHOULD",
486
+
"children": []
487
+
}
488
+
]
438
489
},
439
490
{
440
491
"id": "Requirement 2.5.1",
@@ -453,7 +504,7 @@
453
504
{
454
505
"id": "Requirement 2.6.1",
455
506
"machine_id": "requirement_2_6_1",
456
-
"content": "The provider MAY define an `on context changed` handler, which takes an argument for the previous context and the newly set context, in order to respond to an evaluation context change.",
507
+
"content": "The provider MAY define an `on context changed` function, which takes an argument for the previous context and the newly set context, in order to respond to an evaluation context change.",
457
508
"RFC 2119 keyword": "MAY",
458
509
"children": []
459
510
},
@@ -552,14 +603,14 @@
552
603
{
553
604
"id": "Conditional Requirement 3.2.4.1",
554
605
"machine_id": "conditional_requirement_3_2_4_1",
555
-
"content": "When the global `evaluation context` is set, the `on context changed` handler MUST run.",
606
+
"content": "When the global `evaluation context` is set, the `on context changed` function MUST run.",
556
607
"RFC 2119 keyword": "MUST",
557
608
"children": []
558
609
},
559
610
{
560
611
"id": "Conditional Requirement 3.2.4.2",
561
612
"machine_id": "conditional_requirement_3_2_4_2",
562
-
"content": "When the `evaluation context` for a specific provider is set, the `on context changed` handler MUST only run on the associated provider.",
613
+
"content": "When the `evaluation context` for a specific provider is set, the `on context changed` function MUST only run on the associated provider.",
563
614
"RFC 2119 keyword": "MUST",
564
615
"children": []
565
616
}
@@ -881,6 +932,13 @@
881
932
"RFC 2119 keyword": "SHOULD",
882
933
"children": []
883
934
},
935
+
{
936
+
"id": "Requirement 5.1.5",
937
+
"machine_id": "requirement_5_1_5",
938
+
"content": "`PROVIDER_ERROR` events SHOULD populate the `provider event details`'s `error code` field.",
939
+
"RFC 2119 keyword": "SHOULD",
940
+
"children": []
941
+
},
884
942
{
885
943
"id": "Requirement 5.2.1",
886
944
"machine_id": "requirement_5_2_1",
@@ -960,25 +1018,32 @@
960
1018
{
961
1019
"id": "Conditional Requirement 5.3.4.1",
962
1020
"machine_id": "conditional_requirement_5_3_4_1",
963
-
"content": "When the provider's `on context changed` is called, the provider MAY emit the `PROVIDER_STALE` event, and transition to the `STALE` state.",
964
-
"RFC 2119 keyword": "MAY",
1021
+
"content": "While the provider's `on context changed` function is executing, associated `RECONCILING` handlers MUST run.",
1022
+
"RFC 2119 keyword": "MUST",
965
1023
"children": []
966
1024
},
967
1025
{
968
1026
"id": "Conditional Requirement 5.3.4.2",
969
1027
"machine_id": "conditional_requirement_5_3_4_2",
970
-
"content": "If the provider's `on context changed` function terminates normally, associated `PROVIDER_CONTEXT_CHANGED` handlers MUST run.",
1028
+
"content": "If the provider's `on context changed` function terminates normally, and no other invocations have yet to terminate, associated `PROVIDER_CONTEXT_CHANGED` handlers MUST run.",
971
1029
"RFC 2119 keyword": "MUST",
972
1030
"children": []
973
1031
},
974
1032
{
975
1033
"id": "Conditional Requirement 5.3.4.3",
976
1034
"machine_id": "conditional_requirement_5_3_4_3",
977
-
"content": "If the provider's `on context changed` function terminates abnormally, associated `PROVIDER_ERROR` handlers MUST run.",
1035
+
"content": "If the provider's `on context changed` function terminates abnormally, and no other invocations have yet to terminate, associated `PROVIDER_ERROR` handlers MUST run.",
978
1036
"RFC 2119 keyword": "MUST",
979
1037
"children": []
980
1038
}
981
1039
]
1040
+
},
1041
+
{
1042
+
"id": "Requirement 5.3.5",
1043
+
"machine_id": "requirement_5_3_5",
1044
+
"content": "If the provider emits an event, the value of the client's `provider status` MUST be updated accordingly.",
Copy file name to clipboardExpand all lines: specification/sections/01-flag-evaluation.md
+106
Original file line number
Diff line number
Diff line change
@@ -401,3 +401,109 @@ The global API object might expose a `shutdown` function, which will call the re
401
401
Alternatively, implementations might leverage language idioms such as auto-disposable interfaces or some means of cancellation signal propagation to allow for graceful shutdown.
402
402
403
403
see: [`shutdown`](./02-providers.md#25-shutdown)
404
+
405
+
### 1.7. Provider Lifecycle Management
406
+
407
+
The implementation maintains an internal representation of the state of configured providers, tracking the lifecycle of each provider.
408
+
This state of the provider is exposed on associated `clients`.
409
+
410
+
The diagram below illustrates the possible states and transitions of the `state` field for a provider during the provider lifecycle.
411
+
412
+
```mermaid
413
+
---
414
+
title: Provider lifecycle
415
+
---
416
+
stateDiagram-v2
417
+
direction LR
418
+
[*] --> NOT_READY
419
+
NOT_READY --> READY:initialize()
420
+
NOT_READY --> ERROR:initialize()
421
+
NOT_READY --> FATAL:initialize()
422
+
FATAL --> [*]
423
+
READY --> ERROR:*
424
+
ERROR --> READY:*
425
+
READY --> STALE:*
426
+
STALE --> READY:*
427
+
STALE --> ERROR:*
428
+
READY --> NOT_READY:shutdown()
429
+
STALE --> NOT_READY:shutdown()
430
+
ERROR --> NOT_READY:shutdown()
431
+
READY --> RECONCILING:::client:setContext()
432
+
RECONCILING:::client --> READY
433
+
RECONCILING:::client --> ERROR
434
+
435
+
classDef client fill:#888
436
+
```
437
+
438
+
\* transitions occurring when associated events are spontaneously emitted from the provider
439
+
440
+
<spanstyle="color:#888">█</span> only defined in static-context (client-side) paradigm
441
+
442
+
> [!NOTE]
443
+
> Only SDKs implementing the [static context (client-side) paradigm](../glossary.md#static-context-paradigm) define `RECONCILING` to facilitate [context reconciliation](./02-providers.md#26-provider-context-reconciliation).
444
+
445
+
#### Requirement 1.7.1
446
+
447
+
> The `client`**MUST** define a `provider status` accessor which indicates the readiness of the associated provider, with possible values `NOT_READY`, `READY`, `STALE`, `ERROR`, or `FATAL`.
448
+
449
+
The SDK at all times maintains an up-to-date state corresponding to the success/failure of the last lifecycle method (`initialize`, `shutdown`, `on context change`) or emitted event.
450
+
451
+
see [provider status](../types.md#provider-status)
> In addition to `NOT_READY`, `READY`, `STALE`, or `ERROR`, the `provider status` accessor must support possible value `RECONCILING`.
464
+
465
+
In the static context paradigm, the implementation must define a `provider status` indicating that a provider is reconciling its internal state due to a context change.
466
+
467
+
#### Requirement 1.7.3
468
+
469
+
> The client's `provider status` accessor **MUST** indicate `READY` if the `initialize` function of the associated provider terminates normally.
470
+
471
+
Once the provider has initialized, the `provider status` should indicate the provider is ready to be used to evaluate flags.
472
+
473
+
#### Requirement 1.7.4
474
+
475
+
> The client's `provider status` accessor **MUST** indicate `ERROR` if the `initialize` function of the associated provider terminates abnormally.
476
+
477
+
If the provider has failed to initialize, the `provider status` should indicate the provider is in an error state.
478
+
479
+
#### Requirement 1.7.5
480
+
481
+
> The client's `provider status` accessor **MUST** indicate `FATAL` if the `initialize` function of the associated provider terminates abnormally and indicates `error code``PROVIDER_FATAL`.
482
+
483
+
If the provider has failed to initialize, the `provider status` should indicate the provider is in an error state.
484
+
485
+
#### Requirement 1.7.6
486
+
487
+
> The client **MUST** default, run error hooks, and indicate an error if flag resolution is attempted while the provider is in `NOT_READY`.
488
+
489
+
The client defaults and returns the `PROVIDER_NOT_READY``error code` if evaluation is attempted before the provider is initialized (the provider is still in a `NOT_READY` state).
490
+
The SDK avoids calling the provider's resolver functions entirely ("short-circuits") if the provider is in this state.
491
+
492
+
see: [error codes](../types.md#error-code), [flag value resolution](./02-providers.md#22-flag-value-resolution)
493
+
494
+
#### Requirement 1.7.7
495
+
496
+
> The client **MUST** default, run error hooks, and indicate an error if flag resolution is attempted while the provider is in `PROVIDER_FATAL`.
497
+
498
+
The client defaults and returns the `PROVIDER_FATAL``error code` if evaluation is attempted after the provider has transitioned to an irrecoverable error state.
499
+
The SDK avoids calling the provider's resolver functions entirely ("short-circuits") if the provider is in this state.
500
+
501
+
see: [error codes](../types.md#error-code), [flag value resolution](./02-providers.md#22-flag-value-resolution)
502
+
503
+
#### Requirement 1.7.8
504
+
505
+
> Implementations **SHOULD** propagate the `error code` returned from any provider lifecycle methods.
506
+
507
+
The SDK ensures that if the provider's lifecycle methods terminate with an `error code`, that error code is included in any associated error events and returned/thrown errors/exceptions.
Copy file name to clipboardExpand all lines: specification/sections/02-providers.md
+10-45
Original file line number
Diff line number
Diff line change
@@ -188,53 +188,18 @@ class MyProvider implements Provider {
188
188
}
189
189
```
190
190
191
-
#### Requirement 2.4.2
191
+
#### Condition 2.4.2
192
192
193
-
> The `provider`**MAY** define a `status` field/accessor which indicates the readiness of the provider, with possible values `NOT_READY`, `READY`, `STALE`, or `ERROR`.
193
+
> The provider defines an `initialize` function.
194
194
195
-
Providers without this field can be assumed to be ready immediately.
195
+
##### Conditional Requirement 2.4.2.1
196
196
197
-
The diagram below illustrates the possible states and transitions of the `status` fields.
197
+
> If the provider's `initialize` function fails to render the provider ready to evaluate flags, it **SHOULD** abnormally terminate.
198
198
199
-
```mermaid
200
-
---
201
-
title: Provider State
202
-
---
203
-
stateDiagram-v2
204
-
direction LR
205
-
[*] --> NOT_READY
206
-
NOT_READY --> READY:initialize
207
-
READY --> ERROR
208
-
ERROR --> READY
209
-
READY --> STALE
210
-
STALE --> READY
211
-
STALE --> ERROR
212
-
READY --> NOT_READY:shutdown
213
-
STALE --> NOT_READY:shutdown
214
-
ERROR --> NOT_READY:shutdown
215
-
```
216
-
217
-
see [provider status](../types.md#provider-status)
218
-
219
-
#### Requirement 2.4.3
220
-
221
-
> The provider **MUST** set its `status` field/accessor to `READY` if its `initialize` function terminates normally.
222
-
223
-
If the provider supports the `status` field/accessor and initialization succeeds, setting the `status` to `READY` indicates that the provider is initialized and flag evaluation is proceeding normally.
224
-
225
-
#### Requirement 2.4.4
226
-
227
-
> The provider **MUST** set its `status` field to `ERROR` if its `initialize` function terminates abnormally.
228
-
229
-
If the provider supports the `status` field/accessor and initialization fails, setting the `status` to `ERROR` indicates the provider is in an error state. If the error is transient in nature (ex: a connectivity failure of some kind) the provider can attempt to resolve this state automatically.
230
-
231
-
#### Requirement 2.4.5
232
-
233
-
> The provider **SHOULD** indicate an error if flag resolution is attempted before the provider is ready.
234
-
235
-
It's recommended to set an informative `error code`, such as `PROVIDER_NOT_READY` if evaluation in attempted before the provider is initialized.
199
+
If a provider is unable to start up correctly, it should indicate abnormal execution by throwing an exception, returning an error, or otherwise indicating so by means idiomatic to the implementation language.
200
+
If the error is irrecoverable (perhaps due to bad credentials or invalid configuration) the `PROVIDER_FATAL` error code should be used.
Static-context focused providers may need a mechanism to understand when their cache of evaluated flags must be invalidated or updated. An `on context changed` handler can be defined which performs whatever operations are needed to reconcile the evaluated flags with the new context.
234
+
Static-context focused providers may need a mechanism to understand when their cache of evaluated flags must be invalidated or updated. An `on context changed` function can be defined which performs whatever operations are needed to reconcile the evaluated flags with the new context.
270
235
271
236
#### Requirement 2.6.1
272
237
273
-
> The provider **MAY** define an `on context changed` handler, which takes an argument for the previous context and the newly set context, in order to respond to an evaluation context change.
238
+
> The provider **MAY** define an `on context changed` function, which takes an argument for the previous context and the newly set context, in order to respond to an evaluation context change.
274
239
275
240
Especially in static-context implementations, providers and underlying SDKs may maintain state for a particular context.
276
-
The `on context changed` handler provides a mechanism to update this state, often by re-evaluating flags in bulk with respect to the new context.
241
+
The `on context changed` function provides a mechanism to update this state, often by re-evaluating flags in bulk with respect to the new context.
277
242
278
243
```java
279
244
// MyProvider implementation of the onContextChanged function defined in Provider
0 commit comments