-
Notifications
You must be signed in to change notification settings - Fork 27
feat: Add graceful shutdown support for tokio runtime #71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
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
|
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
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
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
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