open-feature / open-feature/go-sdk
[BUG] EvaluationAPI.Shutdown does not reset API state, so isolated instances retain providers, hooks, context and handlers
- Dominant language
- Go
- Stars
- 249
- Forks
- 61
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 20
Description
## Observed behavior
`EvaluationAPI.Shutdown` shuts providers down, calls `eventExecutor.shutdown()` and `unbindAllProvidersLocked()`, and returns:
https://github.com/open-feature/go-sdk/blob/6b0824c6c7b9b69e40f26aedcc4406ad4d3a5cec/openfeature/openfeature_api.go#L382-L417
It never assigns `a.hks`, `a.evalCtx`, `a.defaultProvider` (not reset to `NoopProvider{}`), or `a.domainProviders`. And `eventExecutor.shutdown()` only touches channels and goroutines — never `apiRegistry`, `scopedRegistry`, `states`, or the provider references:
https://github.com/open-feature/go-sdk/blob/6b0824c6c7b9b69e40f26aedcc4406ad4d3a5cec/openfeature/event_executor.go#L396-L419
On an isolated instance, after `Shutdown`:
```
default provider still active after Shutdown = true (a reset instance would return the default value)
global evaluation context retained = map[global:yes] (targetingKey "tk")
```
Domain providers stay bound, and API-level hooks still run.
The global path masks this. `openfeature.Shutdown()` → `resetSingletonWithContext` builds a **new** `*EvaluationAPI`, swaps it in, and only then shuts the old one down — a clean slate by replacement, not by reset:
https://github.com/open-feature/go-sdk/blob/6b0824c6c7b9b69e40f26aedcc4406ad4d3a5cec/openfeature/openfeature.go#L32-L40
But `isolated.NewAPI()` hands the caller an `*EvaluationAPI` they hold directly, so they get the unreset object. The doc comments on `Shutdown`/`ShutdownWithContext` promise "resets the state of the API, removing all hooks, event handlers, and providers" — true globally, not true of the method itself.
## Expected Behavior
Per [1.6.2](https://openfeature.dev/specification/sections/flag-evaluation#requirement-162), shutdown **MUST** reset all state of the API — hooks, event handlers, evaluation context, transaction context propagators, providers. And per [1.8.2](https://openfeature.dev/specification/sections/flag-evaluation#requirement-182), isolated instances **MUST** conform to the same API contract as the global singleton, *including shutdown functionality*.
Java resets in place, on the same object the caller holds, and that path is shared by `OpenFeatureAPI.createIsolated()`:
```java
providerRepository = new ProviderRepository(this);
eventSupport = new EventSupport();
clearHooks();
setEvaluationContext(ImmutableContext.EMPTY);
setTransactionContextPropagator(new NoOpTransactionContextPropagator());
```
Python does the equivalent (`clear_providers()`, `clear_hooks()`, `clear_evaluation_context()`, `clear_transaction_context_propagator()`).
(Transaction context propagators are N/A here — Go's are `context.Context`-based rather than API state.)
## Steps to reproduce
```go
api := isolated.NewAPI()
ctx := context.Background()
api.SetProviderAndWait(ctx, someProvider) // returns !defaultValue, so it's distinguishable from Noop
api.SetEvaluationContext(of.NewEvaluationContext("tk", map[string]any{"global": "yes"}))
api.Shutdown(ctx)
api.NewClient().Boolean(ctx, "any", false, of.EvaluationContext{}) // still hits someProvider
```
Contributor guide
Research direction
Start in openfeature/openfeature_api.go at Shutdown and ShutdownWithContext, then compare the isolated.NewAPI path with the replacement flow in openfeature/openfeature.go and event_executor.go. Reproduce the isolated-instance scenario from the issue and verify that shutdown removes hooks, handlers, evaluation context, and providers so later evaluations use the reset default behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- api, backend-api-design
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100