Repository navigation
feat(sdk): add explicit connection release to Python and Go clients - #372
Merged
Merged
Conversation
Switch generated namespace structs from `Arc<ClientBuilder>` to `Arc<ClientHandle>`, updating the emitter to import `ClientHandle` alongside `block_on` and adjusting the corresponding test expectations. Also document that every wire enum is intentionally `#[non_exhaustive]` so the provider can add values without a breaking release.
This change lets users release an `FmpClient`'s pooled HTTP connections explicitly and use the client as a context manager. **New client lifecycle:** * Added `FmpClient.close()`, which releases the client's pooled connections and rejects every later call, including calls through a namespace fetched before the close, with `FmpConfigError`. Closing is idempotent and calls already in flight finish normally. * Added `__enter__` and `__exit__` so `with FmpClient(...) as client:` closes the client when the block exits and lets any exception from the block propagate. **Shared client handle:** * Introduced `ClientHandle`, wrapping the `ClientBuilder` with an atomic closed flag shared by the client and every namespace it hands out. The client cache reads the flag under the same mutex `close()` uses to evict, so a closed handle is never re-cached and its pooled client is dropped to release idle connections. * Reworked `runtime::block_on` and all namespaces to take `ClientHandle` instead of `ClientBuilder`, returning a configuration error when the handle is closed.
Add tests verifying that `close()` releases pooled connections, that the
client works as a context manager returning itself, and that closed
clients raise `FmpConfigError("the client is closed")` on any call,
including through namespaces captured before close. Also cover idempotent
close, exception propagation from the `with` block, and isolation between
clients sharing the process-wide runtime.
Document the close and context manager behaviour in the crate README and
the Python wiki.
Add `Client.CloseIdleConnections()`, which closes the idle keep-alive connections of the transport `NewClient` built and leaves the client usable for later requests. When `WithHTTPClient` supplies the `*http.Client`, the call is a no-op so the caller-owned transport and its lifecycle stay untouched. A `Client` owns no resources beyond that connection pool, so there is no `Close`. Track ownership with a new `ownsTransport` flag set when the SDK builds its own transport, and gate the close on it. Cover both paths with tests: one asserts the owned connection is closed and the client still works, the other asserts a caller transport is left alone. Document the method in the Go SDK README and wiki. close #371
The context manager `__exit__` previously returned `False`, which is functionally correct for propagation but relied on returning a boolean. Change it to return `None` so exceptions from the block propagate cleanly and the type stub accurately reflects the behaviour. Added a test asserting `__exit__(None, None, None)` returns `None`.
Strengthen the CloseIdleConnections test by tracking dial counts through the server's ConnState callback. The test now probes the client twice to confirm a single connection is reused before CloseIdleConnections, and verifies that a fresh connection is established afterward.
Update the Python client docs to describe more precisely when an unclosed client releases its pooled connections: on the next request made through any client after garbage collection, or at process exit. This replaces the vaguer "lazily, some time after" wording.
benbenbang
enabled auto-merge (rebase)
September 30, 2026 00:03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Users had no way to explicitly release the pooled HTTP connections held by our SDK clients, forcing them to rely on garbage collection or process exit. This adds first-class lifecycle control across both the Python and Go SDKs.
Python client lifecycle:
FmpClient.close()to release pooled connections and reject all later calls (including through previously fetched namespaces) withFmpConfigError; closing is idempotent and in-flight calls finish normally.with FmpClient(...) as client:closes the client on block exit while propagating any exception, with__exit__returningNone.ClientHandlewrapping the builder with an atomic closed flag, checked under the same mutex used to evict and drop the pooled client, and rework the runtime and every namespace to use it.Go client lifecycle:
Client.CloseIdleConnections()to close idle keep-alive connections on the SDK-owned transport while keeping the client usable; make it a no-op when the transport is caller-supplied viaWithHTTPClient, tracked by a newownsTransportflag.Tests and docs:
#[non_exhaustive].Closes #371