Skip to content

Conversation

@michaelfeil
Copy link
Contributor

@michaelfeil michaelfeil commented Jan 8, 2026

Overview:

I noticed a lot of manual setup's with runtime.shutdown(). Ideally, it would be best to just automatically shutdown the runtime. Some other examples, such as vllm claim that there is a graceful shutdown, but sometimes there is not.

Details:

Where should the reviewer start?

Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

  • closes GitHub issue: #xxx

Summary by CodeRabbit

  • New Features

    • Framework now provides built-in automatic shutdown handling for worker components.
  • Refactor

    • Simplified example worker implementations by removing manual shutdown logic, leveraging framework-provided shutdown management instead.

✏️ Tip: You can customize this high-level summary in your review settings.

Signed-off-by: michaelfeil <me@michaelfeil.eu>
Signed-off-by: michaelfeil <me@michaelfeil.eu>
@michaelfeil michaelfeil requested review from a team as code owners January 8, 2026 13:18
@copy-pr-bot
Copy link

copy-pr-bot bot commented Jan 8, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions
Copy link

github-actions bot commented Jan 8, 2026

👋 Hi michaelfeil! Thank you for contributing to ai-dynamo/dynamo.

Just a reminder: The NVIDIA Test Github Validation CI runs an essential subset of the testing framework to quickly catch errors.Your PR reviewers may elect to test the changes comprehensively before approving your changes.

🚀

@github-actions github-actions bot added external-contribution Pull request is from an external contributor feat labels Jan 8, 2026
@michaelfeil michaelfeil changed the title feat: util register shutdown feat: register shutdown option as part of @dynamo_worker() signature Jan 8, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 8, 2026

Walkthrough

This change refactors shutdown handling across worker components by transitioning from manual signal management to framework-provided automatic shutdown. The Dynamo runtime decorator gains a register_shutdown parameter enabling built-in SIGINT/SIGTERM handling, and six worker examples are updated to leverage this capability.

Changes

Cohort / File(s) Summary
Framework Enhancement
lib/bindings/python/src/dynamo/runtime/__init__.py
Introduces register_shutdown and kv_store parameters to dynamo_worker decorator. Adds _shutdown_handler, _signal_handler, and _register_shutdown_signals helpers to manage signal-based runtime termination. Replaces hardcoded "etcd" with configurable kv_store parameter.
Multimodal Component Workers
examples/multimodal/components/audio_encode_worker.py, encode_worker.py, processor.py, video_encode_worker.py, worker.py
Decorator updated from @dynamo_worker() to @dynamo_worker(register_shutdown=True) across all files. Removes manual signal imports and graceful shutdown handlers (SIGTERM/SIGINT setup, signal callbacks, and associated runtime setup code).
Hello World Example
lib/bindings/python/examples/hello_world/server.py
Decorator updated from @dynamo_worker() to @dynamo_worker(register_shutdown=True). Removes custom graceful_shutdown coroutine and all manual signal-based shutdown orchestration code.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Where signals once danced manual and slow,
Dynamo now steers the shutdown's flow,
Register_shutdown whispers with grace,
Graceful exits, a cleaner space—
Workers frolic, unbound and free! ✨

🚥 Pre-merge checks | ❌ 3
❌ Failed checks (1 warning, 2 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description provides an overview and motivation but lacks specific details about implementation and files changed; the Details and Where to review sections are empty. Complete the Details section to describe the decorator changes and new parameters, and fill Where should the reviewer start with specific files like lib/bindings/python/src/dynamo/runtime/init.py to highlight key changes.
Title check ❓ Inconclusive The title 'feat: util register shutdown' is vague and generic, using non-descriptive terms that don't clearly convey what functionality is being added or modified. Consider a more descriptive title such as 'feat: add automatic shutdown registration to dynamo_worker decorator' or 'feat: enable framework-managed shutdown handling in dynamo_worker' to better clarify the main change.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
examples/multimodal/components/video_encode_worker.py (1)

237-246: Remove unused graceful_shutdown function.

This function is no longer called since manual signal handling was removed. The framework now handles shutdown automatically via _register_shutdown_signals(runtime) when register_shutdown=True.

🧹 Proposed cleanup
-async def graceful_shutdown(runtime):
-    """
-    By calling `runtime.shutdown()`, the endpoints will immediately be unavailable.
-    However, in-flight requests will still be processed until they are finished.
-    After all in-flight requests are finished, the `serve_endpoint` functions will return
-    and the engine will be shutdown by Python's garbage collector.
-    """
-    logging.info("Received shutdown signal, shutting down DistributedRuntime")
-    runtime.shutdown()
-    logging.info("DistributedRuntime shutdown complete")
-
-
examples/multimodal/components/worker.py (1)

397-406: Remove unused graceful_shutdown function.

This function is dead code. The framework now handles shutdown automatically when register_shutdown=True is set on the decorator.

🧹 Proposed cleanup
-async def graceful_shutdown(runtime):
-    """
-    By calling `runtime.shutdown()`, the endpoints will immediately be unavailable.
-    However, in-flight requests will still be processed until they are finished.
-    After all in-flight requests are finished, the `serve_endpoint` functions will return
-    and the engine will be shutdown by Python's garbage collector.
-    """
-    logging.info("Received shutdown signal, shutting down DistributedRuntime")
-    runtime.shutdown()
-    logging.info("DistributedRuntime shutdown complete")
-
-
examples/multimodal/components/processor.py (1)

271-280: Remove unused graceful_shutdown function.

This function is no longer invoked. The framework automatically handles shutdown via _register_shutdown_signals(runtime) when register_shutdown=True.

🧹 Proposed cleanup
-async def graceful_shutdown(runtime):
-    """
-    By calling `runtime.shutdown()`, the endpoints will immediately be unavailable.
-    However, in-flight requests will still be processed until they are finished.
-    After all in-flight requests are finished, the `serve_endpoint` functions will return
-    and the engine will be shutdown by Python's garbage collector.
-    """
-    logging.info("Received shutdown signal, shutting down DistributedRuntime")
-    runtime.shutdown()
-    logging.info("DistributedRuntime shutdown complete")
-
-
examples/multimodal/components/audio_encode_worker.py (1)

233-242: Remove unused graceful_shutdown function.

This function is dead code since manual signal handling was removed. The framework handles shutdown automatically when register_shutdown=True.

🧹 Proposed cleanup
-async def graceful_shutdown(runtime):
-    """
-    By calling `runtime.shutdown()`, the endpoints will immediately be unavailable.
-    However, in-flight requests will still be processed until they are finished.
-    After all in-flight requests are finished, the `serve_endpoint` functions will return
-    and the engine will be shutdown by Python's garbage collector.
-    """
-    logging.info("Received shutdown signal, shutting down DistributedRuntime")
-    runtime.shutdown()
-    logging.info("DistributedRuntime shutdown complete")
-
-
🤖 Fix all issues with AI agents
In @lib/bindings/python/src/dynamo/runtime/__init__.py:
- Around line 31-32: The task created in _signal_handler is not retained and may
be garbage-collected; modify _signal_handler to store the
asyncio.create_task(...) result (e.g., assign it to a durable attribute like
runtime._shutdown_task or a module-level variable) so the Task returned by
asyncio.create_task(_shutdown_handler(runtime)) is kept until completion and can
be cleaned up automatically.
- Around line 25-28: The shutdown handler is defined synchronously but is passed
to asyncio.create_task(), causing a TypeError; change the function signature of
_shutdown_handler to be async (use async def _shutdown_handler(runtime)) so it
returns a coroutine, and keep the call to runtime.shutdown() inside it (it's
safe to call a synchronous method from an async function), ensuring any callers
like asyncio.create_task(_shutdown_handler(runtime)) receive a coroutine.
🧹 Nitpick comments (1)
lib/bindings/python/src/dynamo/runtime/__init__.py (1)

25-28: Consider using logging instead of print statements.

For better observability and consistency with the Rust layer (which uses tracing), consider replacing print statements with Python's logging module.

♻️ Optional: Use logging

Add logging import at the top:

import logging

logger = logging.getLogger(__name__)

Then update the function:

 async def _shutdown_handler(runtime):
-    print("Shutdown signal received, cancelling runtime...")
+    logger.info("Shutdown signal received, cancelling runtime...")
     runtime.shutdown()
-    print("Runtime shutdown complete.")
+    logger.info("Runtime shutdown complete.")
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92748c9 and 1b50974.

📒 Files selected for processing (7)
  • examples/multimodal/components/audio_encode_worker.py
  • examples/multimodal/components/encode_worker.py
  • examples/multimodal/components/processor.py
  • examples/multimodal/components/video_encode_worker.py
  • examples/multimodal/components/worker.py
  • lib/bindings/python/examples/hello_world/server.py
  • lib/bindings/python/src/dynamo/runtime/__init__.py
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: keivenchang
Repo: ai-dynamo/dynamo PR: 4323
File: components/src/dynamo/vllm/main.py:197-205
Timestamp: 2025-11-14T01:09:35.244Z
Learning: keivenchang prefers using graceful_shutdown() over atexit for cleanup logic in components/src/dynamo/vllm/main.py, as it provides better control and consistency with existing shutdown patterns.
Learnt from: nnshah1
Repo: ai-dynamo/dynamo PR: 1444
File: tests/fault_tolerance/utils/metrics.py:30-32
Timestamp: 2025-07-01T13:55:03.940Z
Learning: The `dynamo_worker()` decorator in the dynamo codebase returns a wrapper that automatically injects the `runtime` parameter before calling the wrapped function. This means callers only need to provide the non-runtime parameters, while the decorator handles injecting the runtime argument automatically. For example, a function with signature `async def get_metrics(runtime, log_dir)` decorated with `dynamo_worker()` can be called as `get_metrics(log_dir)` because the decorator wrapper injects the runtime parameter.
📚 Learning: 2025-07-01T13:55:03.940Z
Learnt from: nnshah1
Repo: ai-dynamo/dynamo PR: 1444
File: tests/fault_tolerance/utils/metrics.py:30-32
Timestamp: 2025-07-01T13:55:03.940Z
Learning: The `dynamo_worker()` decorator in the dynamo codebase returns a wrapper that automatically injects the `runtime` parameter before calling the wrapped function. This means callers only need to provide the non-runtime parameters, while the decorator handles injecting the runtime argument automatically. For example, a function with signature `async def get_metrics(runtime, log_dir)` decorated with `dynamo_worker()` can be called as `get_metrics(log_dir)` because the decorator wrapper injects the runtime parameter.

Applied to files:

  • lib/bindings/python/src/dynamo/runtime/__init__.py
  • examples/multimodal/components/encode_worker.py
  • examples/multimodal/components/audio_encode_worker.py
  • examples/multimodal/components/worker.py
  • examples/multimodal/components/processor.py
  • lib/bindings/python/examples/hello_world/server.py
  • examples/multimodal/components/video_encode_worker.py
📚 Learning: 2025-11-14T01:09:35.244Z
Learnt from: keivenchang
Repo: ai-dynamo/dynamo PR: 4323
File: components/src/dynamo/vllm/main.py:197-205
Timestamp: 2025-11-14T01:09:35.244Z
Learning: keivenchang prefers using graceful_shutdown() over atexit for cleanup logic in components/src/dynamo/vllm/main.py, as it provides better control and consistency with existing shutdown patterns.

Applied to files:

  • lib/bindings/python/src/dynamo/runtime/__init__.py
  • examples/multimodal/components/encode_worker.py
  • examples/multimodal/components/audio_encode_worker.py
  • examples/multimodal/components/worker.py
  • lib/bindings/python/examples/hello_world/server.py
  • examples/multimodal/components/video_encode_worker.py
📚 Learning: 2025-09-21T01:40:52.456Z
Learnt from: PeaBrane
Repo: ai-dynamo/dynamo PR: 3155
File: components/backends/vllm/src/dynamo/vllm/main.py:228-233
Timestamp: 2025-09-21T01:40:52.456Z
Learning: In the dynamo codebase, error handling for distributed runtime client initialization (like runtime.namespace().component().endpoint().client()) is handled at the Rust level in the distributed runtime bindings, so Python-level try/catch blocks are not needed and would be redundant.

Applied to files:

  • lib/bindings/python/src/dynamo/runtime/__init__.py
  • lib/bindings/python/examples/hello_world/server.py
📚 Learning: 2026-01-03T01:27:08.763Z
Learnt from: tanmayv25
Repo: ai-dynamo/dynamo PR: 5143
File: examples/multimodal/components/processor.py:207-215
Timestamp: 2026-01-03T01:27:08.763Z
Learning: In examples/multimodal/components/processor.py, ensure the Dynamo multimodal processor supports flexible content ordering in multimodal requests (audio, image, or video content before text), reflecting the OpenAI multimodal API specification where content items can appear in any order. Do not enforce a text-first ordering as mandatory. Implement and verify logic that checks for required content types regardless of position, and update tests to cover all permutations of content ordering.

Applied to files:

  • examples/multimodal/components/processor.py
📚 Learning: 2026-01-03T01:29:46.547Z
Learnt from: tanmayv25
Repo: ai-dynamo/dynamo PR: 5143
File: examples/multimodal/components/processor.py:207-214
Timestamp: 2026-01-03T01:29:46.547Z
Learning: In examples/multimodal/components/processor.py, the processor currently supports only a single text field per request. Do not add support for multiple text fields; this is out of scope for the current implementation. When reviewing or modifying this file, ensure changes do not introduce multi-text-field handling and, if relevant, update tests and documentation to reflect the single-text-field limitation.

Applied to files:

  • examples/multimodal/components/processor.py
🧬 Code graph analysis (7)
lib/bindings/python/src/dynamo/runtime/__init__.py (5)
lib/runtime/src/distributed.rs (3)
  • runtime (288-290)
  • shutdown (323-326)
  • request_plane (382-384)
lib/bindings/python/rust/lib.rs (1)
  • shutdown (616-618)
lib/bindings/python/src/dynamo/_core.pyi (5)
  • shutdown (49-53)
  • shutdown (809-813)
  • shutdown (898-902)
  • DistributedRuntime (36-85)
  • get (1658-1659)
lib/runtime/src/runtime.rs (1)
  • shutdown (302-333)
tests/conftest.py (1)
  • request_plane (515-524)
examples/multimodal/components/encode_worker.py (1)
lib/bindings/python/src/dynamo/runtime/__init__.py (1)
  • dynamo_worker (41-73)
examples/multimodal/components/audio_encode_worker.py (1)
lib/bindings/python/src/dynamo/runtime/__init__.py (1)
  • dynamo_worker (41-73)
examples/multimodal/components/worker.py (1)
lib/bindings/python/src/dynamo/runtime/__init__.py (1)
  • dynamo_worker (41-73)
examples/multimodal/components/processor.py (1)
lib/bindings/python/src/dynamo/runtime/__init__.py (1)
  • dynamo_worker (41-73)
lib/bindings/python/examples/hello_world/server.py (1)
lib/bindings/python/src/dynamo/runtime/__init__.py (1)
  • dynamo_worker (41-73)
examples/multimodal/components/video_encode_worker.py (1)
lib/bindings/python/src/dynamo/runtime/__init__.py (1)
  • dynamo_worker (41-73)
🪛 Ruff (0.14.10)
lib/bindings/python/src/dynamo/runtime/__init__.py

32-32: Store a reference to the return value of asyncio.create_task

(RUF006)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build and Test - dynamo
🔇 Additional comments (9)
examples/multimodal/components/video_encode_worker.py (1)

249-249: LGTM! Framework-managed shutdown enabled.

The decorator change correctly enables automatic SIGINT/SIGTERM handling via the framework's built-in shutdown mechanism, eliminating the need for manual signal handling.

examples/multimodal/components/encode_worker.py (1)

190-190: LGTM! Framework-managed shutdown enabled.

The decorator change correctly enables automatic SIGINT/SIGTERM handling, aligning with the project-wide transition to framework-provided shutdown behavior.

examples/multimodal/components/worker.py (1)

409-409: LGTM! Framework-managed shutdown enabled.

The decorator change correctly transitions shutdown handling to the framework's built-in mechanism, replacing manual signal management.

examples/multimodal/components/processor.py (1)

283-283: LGTM! Framework-managed shutdown enabled.

The decorator change correctly enables automatic shutdown handling, consistent with the project-wide refactoring to centralize shutdown logic in the framework.

examples/multimodal/components/audio_encode_worker.py (1)

245-245: LGTM! Framework-managed shutdown enabled.

The decorator change correctly enables framework-provided shutdown handling, eliminating manual signal management and aligning with the project-wide transition.

lib/bindings/python/src/dynamo/runtime/__init__.py (3)

35-38: Signal registration implementation looks correct.

The signal handling setup properly integrates with the asyncio event loop for SIGINT and SIGTERM. The lambda correctly captures the runtime reference.


41-44: Good API design with backward-compatible defaults.

The new parameters enable opt-in shutdown handling and configurable KV store selection while maintaining backward compatibility.


50-52: Implementation correctly uses configurable parameters and shutdown handling.

The wrapper properly applies the kv_store parameter and conditionally registers signal handlers via the standard asyncio loop.add_signal_handler() pattern when register_shutdown=True. Verification shows selective adoption in examples—only hello_world/server.py enables signal handling, while other examples rely on defaults, which is appropriate for their use cases.

lib/bindings/python/examples/hello_world/server.py (1)

23-23: Excellent simplification by delegating shutdown to the framework.

The transition to register_shutdown=True removes manual signal handling boilerplate while maintaining graceful shutdown behavior. The decorator properly registers SIGINT and SIGTERM handlers through _register_shutdown_signals() when enabled.

Comment on lines +25 to +28
def _shutdown_handler(runtime):
print("Shutdown signal received, cancelling runtime...")
runtime.shutdown()
print("Runtime shutdown complete.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: _shutdown_handler must be an async function.

Line 32 passes _shutdown_handler(runtime) to asyncio.create_task(), which requires a coroutine. Since _shutdown_handler is not defined with async def, this will raise a TypeError at runtime.

🐛 Fix: Define as async function
-def _shutdown_handler(runtime):
+async def _shutdown_handler(runtime):
     print("Shutdown signal received, cancelling runtime...")
     runtime.shutdown()
     print("Runtime shutdown complete.")

Note: runtime.shutdown() is synchronous but can be safely called from an async function.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _shutdown_handler(runtime):
print("Shutdown signal received, cancelling runtime...")
runtime.shutdown()
print("Runtime shutdown complete.")
async def _shutdown_handler(runtime):
print("Shutdown signal received, cancelling runtime...")
runtime.shutdown()
print("Runtime shutdown complete.")
🤖 Prompt for AI Agents
In @lib/bindings/python/src/dynamo/runtime/__init__.py around lines 25 - 28, The
shutdown handler is defined synchronously but is passed to
asyncio.create_task(), causing a TypeError; change the function signature of
_shutdown_handler to be async (use async def _shutdown_handler(runtime)) so it
returns a coroutine, and keep the call to runtime.shutdown() inside it (it's
safe to call a synchronous method from an async function), ensuring any callers
like asyncio.create_task(_shutdown_handler(runtime)) receive a coroutine.

Comment on lines +31 to +32
def _signal_handler(runtime):
asyncio.create_task(_shutdown_handler(runtime))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Store the task reference to prevent premature garbage collection.

The task created by asyncio.create_task() is not stored, which can lead to the task being garbage collected before completion. While unlikely during shutdown, this violates best practices and triggers RUF006.

♻️ Proposed fix: Store task reference
+_shutdown_tasks = set()
+
 def _signal_handler(runtime):
-    asyncio.create_task(_shutdown_handler(runtime))
+    task = asyncio.create_task(_shutdown_handler(runtime))
+    _shutdown_tasks.add(task)
+    task.add_done_callback(_shutdown_tasks.discard)

This ensures the task reference is held until completion and automatically cleaned up.

🧰 Tools
🪛 Ruff (0.14.10)

32-32: Store a reference to the return value of asyncio.create_task

(RUF006)

🤖 Prompt for AI Agents
In @lib/bindings/python/src/dynamo/runtime/__init__.py around lines 31 - 32, The
task created in _signal_handler is not retained and may be garbage-collected;
modify _signal_handler to store the asyncio.create_task(...) result (e.g.,
assign it to a durable attribute like runtime._shutdown_task or a module-level
variable) so the Task returned by
asyncio.create_task(_shutdown_handler(runtime)) is kept until completion and can
be cleaned up automatically.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external-contribution Pull request is from an external contributor feat size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant