Skip to content

fix(streams): close SDK pool when stream startup fails - #96

Merged
tishun merged 5 commits into
feature/resumable-streamsfrom
codex/streams-explicit-clients
Oct 29, 2026
Merged

tishun merged 5 commits into
feature/resumable-streamsfrom
codex/streams-explicit-clients

Conversation

@jruaux

@jruaux jruaux commented Oct 28, 2026 •

Copy link
Copy Markdown

If stream startup raises or is cancelled, redis_lifespan currently exits before entering its cleanup block, leaving the SDK Redis pool open. Start streams inside the existing try/finally so pool teardown still runs when startup fails.

Adds regression tests for startup failure and cancellation. Public APIs, configuration, setup behavior and documentation remain identical to feature/resumable-streams.

Validation: repository nox lint, mypy, Bandit, strict documentation build and the Python 3.12 full suite passed against disposable Redis 8.6.7: 1,336 passed, two skipped, one existing expected failure, approximately 96% coverage. The new regressions failed before the fix and passed afterward. Independent read-only review checked the final diff.

jruaux and others added 3 commits October 28, 2026 18:35
Add environment-free stream policy and app-scoped backend injection for higher-level SDKs. Preserve caller-owned connections, isolate blocking readers, and guard subscriptions across backend restarts.

Cover injected HTTP replay and lifecycle behavior with real Redis scenarios, document the integration contract, and synchronize cancellation checks on observed slot cleanup.
An alternative to PR #1's design that follows the SDK's other features.
It meets the same requirements:

- StreamBackend takes keyword options named after the settings they
  override (key_prefix, active_ttl, heartbeat_interval, max_readers and
  so on), as RedisSessionStore takes key_prefix and idle_ttl.  The
  settings are read once, and only for options left out, so a backend
  given every option reads no REDIS_* variable.  Durations are whole
  seconds or a timedelta.
- add_redis_streams() and .streams() take backend= or the same options,
  as add_redis_sessions() takes store= or store options.  Passing both
  raises StreamConfigurationError.
- One rule starts and stops the backend, so manage_lifespan and the
  lifespan wrapper go: redis_lifespan runs whichever backend is
  registered, and an app without it enters "async with backend".
  initialize() becomes private.
- StreamSettings, from_redis_settings() and check_stream_settings() are
  removed.  The prefix and heartbeat checks move into the constructor;
  the driver socket_timeout check stays at registration.

Kept from PR #1: own clients or a factory, blocking_client and its pool
and timeout checks, restart generations, and the injection tests.
Reject backend options on repeated registration, validate builder-supplied reader clients, and release SDK pools when injected backend startup fails or is cancelled. Add regression coverage for these contracts.
@codecov-commenter

codecov-commenter commented Oct 28, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.14%. Comparing base (8f60da6) to head (fc08a35).

Additional details and impacted files
@@                    Coverage Diff                     @@
##           feature/resumable-streams      #96   +/-   ##
==========================================================
  Coverage                      96.14%   96.14%           
==========================================================
  Files                             17       17           
  Lines                           3737     3738    +1     
==========================================================
+ Hits                            3593     3594    +1     
  Misses                           144      144           
Flag Coverage Δ
integration 79.21% <100.00%> (+<0.01%) ⬆️
unit 85.79% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Restore the feature branch public API, configuration, backend implementation and documentation. Retain only SDK pool cleanup when stream startup fails or is cancelled, with regression coverage.
@jruaux jruaux changed the title feat(streams): support explicit Redis clients and lifecycle ownership fix(streams): close SDK pool when stream startup fails Oct 28, 2026
@jruaux
jruaux marked this pull request as ready for review October 28, 2026 19:29
The rate-limit probe and the cache eviction-safety check also await
Redis before the try block, so cancelling startup during either one
left the pool open.  Run all startup checks inside the try, and cover
each step in the regression test.
@tishun
tishun merged commit 0f29b8b into feature/resumable-streams Oct 29, 2026
21 checks passed
tishun added a commit that referenced this pull request Oct 30, 2026
* feat(streams): support explicit Redis clients and lifecycle ownership

Add environment-free stream policy and app-scoped backend injection for higher-level SDKs. Preserve caller-owned connections, isolate blocking readers, and guard subscriptions across backend restarts.

Cover injected HTTP replay and lifecycle behavior with real Redis scenarios, document the integration contract, and synchronize cancellation checks on observed slot cleanup.

* refactor(streams): use backend options instead of StreamSettings

An alternative to PR #1's design that follows the SDK's other features.
It meets the same requirements:

- StreamBackend takes keyword options named after the settings they
  override (key_prefix, active_ttl, heartbeat_interval, max_readers and
  so on), as RedisSessionStore takes key_prefix and idle_ttl.  The
  settings are read once, and only for options left out, so a backend
  given every option reads no REDIS_* variable.  Durations are whole
  seconds or a timedelta.
- add_redis_streams() and .streams() take backend= or the same options,
  as add_redis_sessions() takes store= or store options.  Passing both
  raises StreamConfigurationError.
- One rule starts and stops the backend, so manage_lifespan and the
  lifespan wrapper go: redis_lifespan runs whichever backend is
  registered, and an app without it enters "async with backend".
  initialize() becomes private.
- StreamSettings, from_redis_settings() and check_stream_settings() are
  removed.  The prefix and heartbeat checks move into the constructor;
  the driver socket_timeout check stays at registration.

Kept from PR #1: own clients or a factory, blocking_client and its pool
and timeout checks, restart generations, and the injection tests.

* fix(streams): validate registration and clean up failed startup

Reject backend options on repeated registration, validate builder-supplied reader clients, and release SDK pools when injected backend startup fails or is cancelled. Add regression coverage for these contracts.

* refactor(streams): remove explicit integration APIs

Restore the feature branch public API, configuration, backend implementation and documentation. Retain only SDK pool cleanup when stream startup fails or is cancelled, with regression coverage.

* fix(lifespan): close the pool when any startup check is canceled

The rate-limit probe and the cache eviction-safety check also await
Redis before the try block, so cancelling startup during either one
left the pool open.  Run all startup checks inside the try, and cover
each step in the regression test.

---------

Co-authored-by: Tihomir Mateev <tihomir.mateev@gmail.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.

3 participants