Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: internal provider state, new client events #241

Merged
merged 29 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8864a53
feat: internal provider state, new client events
toddbaert Feb 5, 2024
67cdee6
Update specification/sections/01-flag-evaluation.md
toddbaert Feb 6, 2024
e811565
Update specification/sections/05-events.md
toddbaert Feb 6, 2024
0386ecd
Update specification/sections/05-events.md
toddbaert Feb 6, 2024
b67b455
Update specification/types.md
toddbaert Feb 6, 2024
d3f42be
Update specification/sections/01-flag-evaluation.md
toddbaert Feb 6, 2024
5c649a5
fixup: feedback
toddbaert Feb 6, 2024
d64ec3d
Update specification/sections/05-events.md
toddbaert Feb 7, 2024
ef3ecc2
Update specification/sections/01-flag-evaluation.md
toddbaert Feb 7, 2024
c787c3c
fixup: short-circuiing, event rename
toddbaert Feb 8, 2024
4621fe7
Update specification/sections/05-events.md
toddbaert Feb 8, 2024
7d8e327
Update specification/sections/05-events.md
toddbaert Feb 8, 2024
02d79d6
Update specification/sections/05-events.md
toddbaert Feb 8, 2024
138f370
Update specification/sections/01-flag-evaluation.md
toddbaert Feb 8, 2024
6a9c4bb
Update specification/sections/01-flag-evaluation.md
toddbaert Feb 8, 2024
5229946
Update specification/sections/02-providers.md
toddbaert Feb 8, 2024
aef9a22
Update specification/sections/01-flag-evaluation.md
toddbaert Feb 8, 2024
c90a094
fixup: links
toddbaert Feb 8, 2024
e3a22c0
Update specification/sections/01-flag-evaluation.md
toddbaert Feb 8, 2024
66050db
fixup: 888
toddbaert Feb 8, 2024
b7e3f4b
fixup: must
toddbaert Feb 8, 2024
284becc
fixup: parse
toddbaert Feb 8, 2024
b11cb33
Update specification/sections/05-events.md
toddbaert Feb 9, 2024
a3f2065
fixup: rm PROVIDER_CONTEXT_CHANGE_PENDING
toddbaert Feb 9, 2024
20aa92a
Update specification/sections/05-events.md
toddbaert Feb 9, 2024
aa38d5f
fixup: add RECONCILING -> ERROR transition
toddbaert Feb 20, 2024
9f717a9
fixup: add title to state mermaid
toddbaert Feb 20, 2024
1e53720
fixup: include error and note in state reconcilation mermaid
toddbaert Feb 20, 2024
634f2b5
fixup: context change reentrancy considerations
toddbaert Feb 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 98 additions & 33 deletions specification.json
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,70 @@
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Requirement 1.7.1",
"machine_id": "requirement_1_7_1",
"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`.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Condition 1.7.2",
"machine_id": "condition_1_7_2",
"content": "The implementation uses the static-context paradigm.",
"RFC 2119 keyword": null,
"children": [
{
"id": "Conditional Requirement 1.7.2.1",
"machine_id": "conditional_requirement_1_7_2_1",
"content": "In addition to `NOT_READY`, `READY`, `STALE`, or `ERROR`, the `provider status` accessor must support possible value `RECONCILING`.",
"RFC 2119 keyword": null,
"children": []
}
]
},
{
"id": "Requirement 1.7.3",
"machine_id": "requirement_1_7_3",
"content": "The client's `provider status` accessor MUST indicate `READY` if the `initialize` function of the associated provider terminates normally.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Requirement 1.7.4",
"machine_id": "requirement_1_7_4",
"content": "The client's `provider status` accessor MUST indicate `ERROR` if the `initialize` function of the associated provider terminates abnormally.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Requirement 1.7.5",
"machine_id": "requirement_1_7_5",
"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`.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Requirement 1.7.6",
"machine_id": "requirement_1_7_6",
"content": "The client MUST default, run error hooks, and indicate an error if flag resolution is attempted while the provider is in `NOT_READY`.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Requirement 1.7.7",
"machine_id": "requirement_1_7_7",
"content": "The client MUST default, run error hooks, and indicate an error if flag resolution is attempted while the provider is in `PROVIDER_FATAL`.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Requirement 1.7.8",
"machine_id": "requirement_1_7_8",
"content": "Implementations SHOULD propagate the `error code` returned from any provider lifecycle methods.",
"RFC 2119 keyword": "SHOULD",
"children": []
},
{
"id": "Requirement 2.1.1",
"machine_id": "requirement_2_1_1",
Expand Down Expand Up @@ -409,32 +473,19 @@
"children": []
},
{
"id": "Requirement 2.4.2",
"machine_id": "requirement_2_4_2",
"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`.",
"RFC 2119 keyword": "MAY",
"children": []
},
{
"id": "Requirement 2.4.3",
"machine_id": "requirement_2_4_3",
"content": "The provider MUST set its `status` field/accessor to `READY` if its `initialize` function terminates normally.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Requirement 2.4.4",
"machine_id": "requirement_2_4_4",
"content": "The provider MUST set its `status` field to `ERROR` if its `initialize` function terminates abnormally.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Requirement 2.4.5",
"machine_id": "requirement_2_4_5",
"content": "The provider SHOULD indicate an error if flag resolution is attempted before the provider is ready.",
"RFC 2119 keyword": "SHOULD",
"children": []
"id": "Condition 2.4.2",
"machine_id": "condition_2_4_2",
"content": "The provider defines an `initialize` function.",
"RFC 2119 keyword": null,
"children": [
{
"id": "Conditional Requirement 2.4.2.1",
"machine_id": "conditional_requirement_2_4_2_1",
"content": "If the provider's `initialize` function fails to render the provider ready to evaluate flags, it SHOULD abnormally terminate.",
"RFC 2119 keyword": "SHOULD",
"children": []
}
]
},
{
"id": "Requirement 2.5.1",
Expand All @@ -453,7 +504,7 @@
{
"id": "Requirement 2.6.1",
"machine_id": "requirement_2_6_1",
"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.",
"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.",
"RFC 2119 keyword": "MAY",
"children": []
},
Expand Down Expand Up @@ -552,14 +603,14 @@
{
"id": "Conditional Requirement 3.2.4.1",
"machine_id": "conditional_requirement_3_2_4_1",
"content": "When the global `evaluation context` is set, the `on context changed` handler MUST run.",
"content": "When the global `evaluation context` is set, the `on context changed` function MUST run.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Conditional Requirement 3.2.4.2",
"machine_id": "conditional_requirement_3_2_4_2",
"content": "When the `evaluation context` for a specific provider is set, the `on context changed` handler MUST only run on the associated provider.",
"content": "When the `evaluation context` for a specific provider is set, the `on context changed` function MUST only run on the associated provider.",
"RFC 2119 keyword": "MUST",
"children": []
}
Expand Down Expand Up @@ -881,6 +932,13 @@
"RFC 2119 keyword": "SHOULD",
"children": []
},
{
"id": "Requirement 5.1.5",
"machine_id": "requirement_5_1_5",
"content": "`PROVIDER_ERROR` events SHOULD populate the `provider event details`'s `error code` field.",
"RFC 2119 keyword": "SHOULD",
"children": []
},
{
"id": "Requirement 5.2.1",
"machine_id": "requirement_5_2_1",
Expand Down Expand Up @@ -960,25 +1018,32 @@
{
"id": "Conditional Requirement 5.3.4.1",
"machine_id": "conditional_requirement_5_3_4_1",
"content": "When the provider's `on context changed` is called, the provider MAY emit the `PROVIDER_STALE` event, and transition to the `STALE` state.",
"RFC 2119 keyword": "MAY",
"content": "While the provider's `on context changed` function is executing, associated `RECONCILING` handlers MUST run.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Conditional Requirement 5.3.4.2",
"machine_id": "conditional_requirement_5_3_4_2",
"content": "If the provider's `on context changed` function terminates normally, associated `PROVIDER_CONTEXT_CHANGED` handlers MUST run.",
"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.",
"RFC 2119 keyword": "MUST",
"children": []
},
{
"id": "Conditional Requirement 5.3.4.3",
"machine_id": "conditional_requirement_5_3_4_3",
"content": "If the provider's `on context changed` function terminates abnormally, associated `PROVIDER_ERROR` handlers MUST run.",
"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.",
"RFC 2119 keyword": "MUST",
"children": []
}
]
},
{
"id": "Requirement 5.3.5",
"machine_id": "requirement_5_3_5",
"content": "If the provider emits an event, the value of the client's `provider status` MUST be updated accordingly.",
"RFC 2119 keyword": "MUST",
"children": []
}
]
}
106 changes: 106 additions & 0 deletions specification/sections/01-flag-evaluation.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,3 +401,109 @@ The global API object might expose a `shutdown` function, which will call the re
Alternatively, implementations might leverage language idioms such as auto-disposable interfaces or some means of cancellation signal propagation to allow for graceful shutdown.

see: [`shutdown`](./02-providers.md#25-shutdown)

### 1.7. Provider Lifecycle Management

The implementation maintains an internal representation of the state of configured providers, tracking the lifecycle of each provider.
This state of the provider is exposed on associated `clients`.

The diagram below illustrates the possible states and transitions of the `state` field for a provider during the provider lifecycle.

```mermaid
---
title: Provider lifecycle
---
stateDiagram-v2
direction LR
[*] --> NOT_READY
NOT_READY --> READY:initialize()
NOT_READY --> ERROR:initialize()
NOT_READY --> FATAL:initialize()
FATAL --> [*]
READY --> ERROR:*
ERROR --> READY:*
READY --> STALE:*
STALE --> READY:*
STALE --> ERROR:*
READY --> NOT_READY:shutdown()
STALE --> NOT_READY:shutdown()
ERROR --> NOT_READY:shutdown()
READY --> RECONCILING:::client:setContext()
RECONCILING:::client --> READY
toddbaert marked this conversation as resolved.
Show resolved Hide resolved
RECONCILING:::client --> ERROR

classDef client fill:#888
```

\* transitions occurring when associated events are spontaneously emitted from the provider

<span style="color:#888">█</span> only defined in static-context (client-side) paradigm

> [!NOTE]
> 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).

#### Requirement 1.7.1

> 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`.

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.
toddbaert marked this conversation as resolved.
Show resolved Hide resolved

see [provider status](../types.md#provider-status)

#### Condition 1.7.2

[![experimental](https://img.shields.io/static/v1?label=Status&message=experimental&color=orange)](https://github.com/open-feature/spec/tree/main/specification#experimental)

> The implementation uses the static-context paradigm.

see: [static-context paradigm](../glossary.md#static-context-paradigm)

##### Conditional Requirement 1.7.2.1

> In addition to `NOT_READY`, `READY`, `STALE`, or `ERROR`, the `provider status` accessor must support possible value `RECONCILING`.

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.

#### Requirement 1.7.3

> The client's `provider status` accessor **MUST** indicate `READY` if the `initialize` function of the associated provider terminates normally.

Once the provider has initialized, the `provider status` should indicate the provider is ready to be used to evaluate flags.

#### Requirement 1.7.4

> The client's `provider status` accessor **MUST** indicate `ERROR` if the `initialize` function of the associated provider terminates abnormally.

If the provider has failed to initialize, the `provider status` should indicate the provider is in an error state.

#### Requirement 1.7.5

> 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`.

If the provider has failed to initialize, the `provider status` should indicate the provider is in an error state.

#### Requirement 1.7.6

> The client **MUST** default, run error hooks, and indicate an error if flag resolution is attempted while the provider is in `NOT_READY`.

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).
The SDK avoids calling the provider's resolver functions entirely ("short-circuits") if the provider is in this state.

see: [error codes](../types.md#error-code), [flag value resolution](./02-providers.md#22-flag-value-resolution)

#### Requirement 1.7.7

> The client **MUST** default, run error hooks, and indicate an error if flag resolution is attempted while the provider is in `PROVIDER_FATAL`.

The client defaults and returns the `PROVIDER_FATAL` `error code` if evaluation is attempted after the provider has transitioned to an irrecoverable error state.
The SDK avoids calling the provider's resolver functions entirely ("short-circuits") if the provider is in this state.

see: [error codes](../types.md#error-code), [flag value resolution](./02-providers.md#22-flag-value-resolution)

#### Requirement 1.7.8

> Implementations **SHOULD** propagate the `error code` returned from any provider lifecycle methods.

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.

see: [error codes](../types.md#error-code)
55 changes: 10 additions & 45 deletions specification/sections/02-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,53 +188,18 @@ class MyProvider implements Provider {
}
```

#### Requirement 2.4.2
#### Condition 2.4.2

> The `provider` **MAY** define a `status` field/accessor which indicates the readiness of the provider, with possible values `NOT_READY`, `READY`, `STALE`, or `ERROR`.
> The provider defines an `initialize` function.

Providers without this field can be assumed to be ready immediately.
##### Conditional Requirement 2.4.2.1

The diagram below illustrates the possible states and transitions of the `status` fields.
> If the provider's `initialize` function fails to render the provider ready to evaluate flags, it **SHOULD** abnormally terminate.

```mermaid
---
title: Provider State
---
stateDiagram-v2
direction LR
[*] --> NOT_READY
NOT_READY --> READY:initialize
READY --> ERROR
ERROR --> READY
READY --> STALE
STALE --> READY
STALE --> ERROR
READY --> NOT_READY:shutdown
STALE --> NOT_READY:shutdown
ERROR --> NOT_READY:shutdown
```

see [provider status](../types.md#provider-status)

#### Requirement 2.4.3

> The provider **MUST** set its `status` field/accessor to `READY` if its `initialize` function terminates normally.

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.

#### Requirement 2.4.4

> The provider **MUST** set its `status` field to `ERROR` if its `initialize` function terminates abnormally.

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.

#### Requirement 2.4.5

> The provider **SHOULD** indicate an error if flag resolution is attempted before the provider is ready.

It's recommended to set an informative `error code`, such as `PROVIDER_NOT_READY` if evaluation in attempted before the provider is initialized.
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.
If the error is irrecoverable (perhaps due to bad credentials or invalid configuration) the `PROVIDER_FATAL` error code should be used.

see: [error codes](https://openfeature.dev/specification/types#error-code)
see: [error codes](../types.md#error-code)

### 2.5. Shutdown

Expand Down Expand Up @@ -266,14 +231,14 @@ see: [initialization](#24-initialization)

[![experimental](https://img.shields.io/static/v1?label=Status&message=experimental&color=orange)](https://github.com/open-feature/spec/tree/main/specification#experimental)

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.
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.

#### Requirement 2.6.1

> 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.
> 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.

Especially in static-context implementations, providers and underlying SDKs may maintain state for a particular context.
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.
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.

```java
// MyProvider implementation of the onContextChanged function defined in Provider
Expand Down
Loading
Loading