feat: Add graceful shutdown support for tokio runtime#71
Open
feat: Add graceful shutdown support for tokio runtime#71
Conversation
achimnol
added a commit
to lablup/etcd-client-py
that referenced
this pull request
Jan 5, 2026
Refactored code for upstream PR review: - Add section comments for better code organization - Remove debug eprintln! statements - Add get_handle() as the recommended public API - Improve documentation for all public functions - Mark get_runtime() as deprecated with migration guidance PR: PyO3/pyo3-async-runtimes#71
Author
|
I have worked with Claude Code to resolve the runtime cleanup issues in our library ( Also refer to the test cases in |
1b266e5 to
da42ef5
Compare
achimnol
added a commit
to lablup/etcd-client-py
that referenced
this pull request
Jan 9, 2026
Refines PR PyO3/pyo3-async-runtimes#71 for review: - Squashed 17 commits into single clean commit - Updated macros to use new spawn_blocking() API - Added #[allow(deprecated)] where get_runtime() is still needed - All tests pass
Prepare dependencies for graceful shutdown support: - Replace `futures` with `futures-channel` + `futures-util` for reduced dependency tree - Add `parking_lot` for RwLock (needed for runtime wrapper) - Add tokio `sync` feature for Notify (shutdown signaling) - Add test entry for shutdown tests
Implement dedicated runtime thread pattern (inspired by valkey-glide): RuntimeWrapper: - Manages tokio runtime in dedicated "pyo3-tokio-runtime" thread - Exposes Handle for thread-safe task spawning - Supports graceful shutdown via Notify signaling New public APIs: - get_handle() -> Handle: Returns cloneable handle (recommended) - spawn(fut) / spawn_blocking(f): Convenience spawning functions - request_shutdown(timeout_ms): Blocking shutdown, waits for completion - request_shutdown_background(timeout_ms): Non-blocking for async contexts - join_pending_shutdown(py): Join pending background shutdown Deprecated: - get_runtime(): Cannot be gracefully shut down, use get_handle() Implementation details: - Runtime lives in dedicated thread, accessed only via Handle - Shutdown signals thread via Notify, sends timeout via channel - Thread calls Runtime::shutdown_timeout() then terminates - Runtime slot cleared after shutdown, allowing re-initialization Fixes PyO3#40
…nsistency Add matching APIs to async-std module for consistency with tokio: New public APIs: - spawn(fut): Wrapper for async_std::task::spawn - spawn_blocking(f): Wrapper for async_std::task::spawn_blocking - request_shutdown(timeout_ms): Sets internal flag only Note: async-std uses a global runtime model without explicit lifecycle management. Unlike tokio, there's no way to actually shut down the runtime. These APIs provide a consistent interface but request_shutdown only sets a flag for API compatibility. Internal changes: - Add RuntimeWrapper struct for API consistency - Add section comments matching tokio module organization
Update proc macros to work with new shutdown-aware APIs: tokio_test macro: - Replace get_runtime().spawn_blocking() with spawn_blocking() - Uses the new module-level function that works with RuntimeWrapper tokio_main macro: - Add #[allow(deprecated)] for get_runtime().block_on() usage - block_on() requires Runtime reference, not available via Handle - This is intentional: main macro keeps runtime alive for current_thread
Test updates: - Add test_tokio_shutdown.rs: Tests shutdown and re-initialization - Add tests for spawn(), spawn_blocking(), get_handle() in tokio_asyncio - Update tokio_run_forever to use spawn() instead of get_runtime().spawn() Deprecation handling in tests: - Add #[allow(deprecated)] for tests that intentionally use get_runtime() - Tests using LocalSet::block_on() need Runtime reference (not Handle) - Use local variable binding pattern for clean allow annotations Other changes: - Update CHANGELOG.md with new APIs and deprecations - Update futures import to futures_util in generic.rs and lib.rs - Update testing.rs futures import
da42ef5 to
97414ae
Compare
achimnol
added a commit
to lablup/etcd-client-py
that referenced
this pull request
Jan 9, 2026
Updates vendored pyo3-async-runtimes (PyO3/pyo3-async-runtimes#71) with cleaner commit history: 1. deps: Replace futures with futures-channel/futures-util, add parking_lot 2. feat(tokio): Add RuntimeWrapper with graceful shutdown support 3. feat(async-std): Add spawn/spawn_blocking/request_shutdown for API consistency 4. refactor(macros): Update to use new spawn_blocking API 5. test: Add shutdown tests and update existing tests for deprecated API
Merge 4 commits from upstream/main (22e0ec1 → 19176f5): - f17e3b0: Bump MSRV to 1.83 - 1289ad4: Replace futures crate with futures-channel/futures-util - d78a63b: Check for awaitable instead of coroutine in stream glue - 19176f5: Update README's MSRV Conflict resolution: - Cargo.toml: Use upstream version (no explicit features for futures-*) - CHANGELOG.md: Combine both changes (shutdown API + MSRV/bug fixes) - src/*.rs: Use upstream import paths (futures_util::stream::Stream) - src/tokio.rs: Keep fork's concise stream function definitions, remove duplicate function definitions from upstream conflict region - pytests/*.rs: Combine #[allow(deprecated)] with futures_util imports
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.
Summary
This PR adds graceful shutdown support for the tokio runtime, addressing #40.
Motivation
When Python extensions built with pyo3-async-runtimes are used in subprocesses or short-lived contexts, tokio tasks may still be running when Python interpreter finalization begins. This causes fatal errors like:
Real-world Use Case: etcd-client-py
This implementation enables proper shutdown coordination as demonstrated in lablup/etcd-client-py#17, which uses the new APIs to implement automatic runtime cleanup:
ACTIVE_CONTEXTSatomic counter tracks client contexts__aexit__:_trigger_shutdown()(wrapsrequest_shutdown_background)asyncio.to_thread()to block-join the runtime (wrapsjoin_pending_shutdown)This ensures shutdown completes within the async context, avoiding deadlocks and race conditions where the runtime shuts down mid-task.
Implementation
The tokio runtime now lives in a dedicated thread (inspired by valkey-glide):
When
request_shutdown(timeout_ms)is called:Runtime::shutdown_timeout()request_shutdownblocks onthread.join()until completeNew APIs
Tokio
get_handle() -> Handlespawn(fut)spawn_blocking(f)request_shutdown(timeout_ms) -> boolrequest_shutdown_background(timeout_ms) -> booljoin_pending_shutdown(py) -> boolasync-std (API consistency)
spawn(fut)async_std::task::spawnspawn_blocking(f)async_std::task::spawn_blockingrequest_shutdown(timeout_ms) -> boolDeprecated APIs
tokio::get_runtime()- Cannot be gracefully shut down. Useget_handle()instead.Usage Example
Dependency Changes
futureswithfutures-channel+futures-utilfor reduced dependency treeparking_lotfor RwLocksyncfeature for NotifyBackward Compatibility
future_into_pyworks unchangedget_runtime()is deprecated but still functional (uses a separate leaked runtime)Testing
Tested in etcd-client-py across:
Related