Skip to content

[openfeature] add close() to the adapter - #476

Draft
dferber90 wants to merge 1 commit into
mainfrom
close
Draft

[openfeature] add close() to the adapter#476
dferber90 wants to merge 1 commit into
mainfrom
close

Conversation

@dferber90

@dferber90 dferber90 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #474, which corrected the initialization retry behavior and closed #473. This PR contains only the shutdown mechanism.

The condition

The adapter had no shutdown mechanism. Section 2.5 of the OpenFeature provider specification gives requirements for such a mechanism.

The new close() function

  • close() puts the adapter in its uninitialized state. The next evaluation initializes the adapter again. See requirement 2.5.2.
  • If an initialization is in progress, close() first waits for the initialization. The client then goes to onClose, and the adapter does not leak the client. If an evaluation starts during the shutdown, the evaluation waits, then it initializes the adapter again. See requirement 2.5.2.
  • close() is idempotent. Concurrent calls share one shutdown. A second call does nothing more, if no initialization occurred between the two calls. See requirement 2.5.3.
  • If the initialization failed, there is nothing to dispose of. close() then does not call onClose.

Requirement 2.5.1 is about the disposal of resources. The new onClose option does this, because the adapter cannot do it correctly:

createOpenFeatureAdapter(init, { onClose: () => OpenFeature.close() });

An OpenFeature client has no function to close itself. To shut down a provider, you must close the provider on the global OpenFeature API. But that also closes providers that this adapter did not register. Thus the user must give permission for the disposal.

The new options parameter is an addition to both overloads. Existing code continues to operate.

Be careful: if you give a Client to the adapter, and not an init function, the adapter has nothing to make again. Evaluations after close() then continue to use the same client. The README gives this information.

Tests

There are 7 new tests for close(). I also did mutation tests on the two most difficult lines: the guard for a shutdown in progress in initialize(), and the wait for the initialization in progress. If you remove one of these two lines, one test fails.

All 21 tests, pnpm type-check, pnpm check, and pnpm build are successful.

Specification

https://openfeature.dev/specification/sections/providers#25-shutdown

🤖 Generated with Claude Code

@vercel

vercel Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
flags-playground Ready Ready Preview Aug 15, 2026 5:07am
flags-sdk-dev Ready Ready Preview Aug 15, 2026 5:07am
flags-sdk-snippets Ready Ready Preview Aug 15, 2026 5:07am
flags-sdk-sveltekit-snippets Ready Ready Preview Aug 15, 2026 5:07am
shirt-shop Ready Ready Preview Aug 15, 2026 5:07am
shirt-shop-api Ready Ready Preview Aug 15, 2026 5:07am

The adapter had no shutdown mechanism. close() now reverts it to its
uninitialized state, so the next evaluation initializes it again, per
section 2.5 of the OpenFeature provider spec. It first awaits an
initialization that is still in flight, so the client that initialization
produces is disposed rather than leaked, and it is idempotent.

Provider disposal is opt-in through the new onClose option, because the
adapter cannot do it on its own: an OpenFeature client has no close of
its own, and shutting down providers means closing them on the global
OpenFeature API, which would also affect providers this adapter never
registered.

Follow-up to #474, which fixed #473.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

createOpenFeatureAdapter caches a rejected init promise forever, permanently serving defaultValue

1 participant