Repository navigation
polish(transport): make Sync/AsyncTransport.close() explicitly idempotent (#301) - #309
Conversation
…tent - Add a `_closed` flag set in `__init__` and gate `close()` / async `close()` so the second and subsequent calls are no-ops on the underlying httpx client (stacked cleanup: context-manager exits, `atexit`, explicit teardown in tests). - Document idempotence in both close docstrings; raising on use-after-close is intentionally out of scope (separate concern). - Add `test_sync_transport_close_is_idempotent` and `test_async_transport_close_is_idempotent` mirroring the existing `KalshiAuth.test_close_is_idempotent` triple-close pattern. Closes #301.
Code ReviewOverviewThis PR makes One Concern: Thread Safety Gap in
|
Addresses round-1 review on #309: - Sync close() docstring now explicitly bounds the idempotence contract to sequential callers; concurrent threads degrade to one redundant httpx.Client.close() (itself idempotent). - Async close() docstring notes cooperative scheduling makes it race-free. Skipping a threading.Lock — the KalshiAuth precedent (#267) uses one because get_sign_executor() can recreate the executor under contention, which has no analogue here. SyncTransport.close() is a single httpx close + flag flip; the redundant-close worst case is benign by virtue of httpx own idempotence.
|
Round-1 review addressed in follow-up:
|
Code Review — PR #309: Idempotent
|
…ency with auth.py)
|
Round-2 nit applied (305e... incoming): explicit Merging on green. |
Code Review — PR #309: Make
|
|
Round-3 review: zero blocking items. All three observations explicitly non-blocking:
Merging. |
Closes #301.
What
SyncTransport.close()andAsyncTransport.close()(async) were the only lifecycle methods in the SDK without an explicit_closedflag.httpx.Client.close()happens to be idempotent today, but the SDK pattern (KalshiAuth,KalshiClient) tracks_closedexplicitly — #267 even added a re-check-under-lock guard to the sign executor. This PR brings the transports in line._closed: boolset in both__init__s.close()/aclose()early-return if already closed; otherwise flip the flag and delegate to httpx.Tests
TestSyncTransportLifecycle.test_sync_transport_close_is_idempotentTestAsyncTransportLifecycle.test_async_transport_close_is_idempotentBoth triple-close and assert
_closed is Trueafter the first call — mirrorstests/test_auth.py::test_close_is_idempotent.Final PR of the post-v2.5 audit closure (Wave W2).