open-feature / open-feature/go-sdk
[BUG] reusing an API instance after Shutdown panics with "send on closed channel"
- Dominant language
- Go
- Stars
- 249
- Forks
- 61
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 20
Description
## Observed behavior
`eventExecutor.shutdown()` **closes** every active subscription's semaphore rather than sending to it:
https://github.com/open-feature/go-sdk/blob/6b0824c6c7b9b69e40f26aedcc4406ad4d3a5cec/openfeature/event_executor.go#L396-L419
A later `SetProvider` reaches `startListeningAndShutdownOld`. The previous reference is no longer bound (the new provider already replaced `defaultProviderReference` earlier in `registerDefaultProvider`), so control reaches the shutdown-signal send:
https://github.com/open-feature/go-sdk/blob/6b0824c6c7b9b69e40f26aedcc4406ad4d3a5cec/openfeature/event_executor.go#L256-L293
A closed channel is always ready for send, so `select` picks that case and the send panics — the `default` cannot rescue it. The comment above it reasons about buffer capacity, but not about closure:
```
panic: send on closed channel
event_executor.go:276 startListeningAndShutdownOld
event_executor.go:205 registerDefaultProvider
openfeature_api.go:215 setProvider
```
The panic escapes into the caller's goroutine from `SetProvider`, taking the process down.
There is a quieter variant too. Re-register on a *domain* instead and there's no panic, but `startEventListener` is guarded by `e.once` (already spent) and `e.done` is closed — so no listener goroutine is started and events from the new provider are silently dropped until `eventChan` fills (cap 5).
## Expected Behavior
Either reuse works, or it fails with an error — but not a panic, and not silent event loss.
Java explicitly supports reuse (Javadoc on `shutdown`: *"Once shut down is complete, API is reset and ready to use again"*) and guards the window with an `isShuttingDown` flag, throwing `IllegalStateException` on a concurrent `setProvider` rather than corrupting state.
## Relationship to #495 / #523
#523 (draft) adds an active/shutdown state that rejects registration after shutdown, which would prevent the common path into this panic. Filing separately for two reasons:
1. The underlying close-then-send hazard in `event_executor.go` is untouched by that PR. Notably #523 restores `evaluationAPIStateActive` when shutdown is cancelled via `ctx.Done()`, so a subsequent `SetProvider` can still reach the same closed semaphore.
2. The dead-listener half (spent `once`, closed `done`) is a separate defect from registration gating, and would remain even with the guard in place.
There's also a design question worth settling alongside #554: if `Shutdown` is meant to *reset* the API per [1.6.2](https://openfeature.dev/specification/sections/flag-evaluation#requirement-162), then permanently poisoning the instance is the wrong end state, and the executor would need to be re-armed rather than gated. Happy to fold this into #523 if maintainers prefer.
## Steps to reproduce
```go
api := isolated.NewAPI()
ctx := context.Background()
// p and q are pointer-type providers implementing of.EventHandler
p, q := newEventEmittingProvider(), newEventEmittingProvider()
api.SetProviderAndWait(ctx, p)
api.Shutdown(ctx)
api.SetProviderAndWait(ctx, q) // panic: send on closed channel
```
Contributor guide
Research direction
Start with openfeature/event_executor.go, especially eventExecutor.shutdown, startListeningAndShutdownOld, startEventListener, and registerDefaultProvider. Run the reproduction from the issue with isolated.NewAPI, then verify that reusing the API after Shutdown neither panics in SetProviderAndWait nor drops new-provider events when registering on a domain.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- api
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100