Skip to content

Fix graceful shutdown on SIGTERM#1396

Open
ahmadmahmoody wants to merge 1 commit into
sparckles:mainfrom
ahmadmahmoody:am-fix-graceful-shutdown-1324
Open

Fix graceful shutdown on SIGTERM#1396
ahmadmahmoody wants to merge 1 commit into
sparckles:mainfrom
ahmadmahmoody:am-fix-graceful-shutdown-1324

Conversation

@ahmadmahmoody
Copy link
Copy Markdown

@ahmadmahmoody ahmadmahmoody commented May 29, 2026

Description

This PR fixes #1324.

Summary

This PR makes SIGTERM trigger Robyn's graceful shutdown path instead of requiring callers to use process.kill() as a workaround. It adds a fixed shutdown grace period before falling back to force-kill behavior, updates the dev reloader to wait for graceful termination, and adds a POSIX integration regression test that verifies process.terminate() runs @app.shutdown_handler.

PR Checklist

Please ensure that:

  • The PR contains a descriptive title
  • The PR contains a descriptive summary of the changes
  • You build and test your changes before submitting a PR.
  • You have added relevant documentation
  • You have added relevant tests. We prefer integration tests wherever possible

Pre-Commit Instructions:

Documentation note: no documentation update was added because this fixes existing shutdown behavior and adds regression coverage.

Summary by CodeRabbit

Release Notes

  • New Features

    • Graceful shutdown support: applications now properly handle termination signals and execute registered shutdown handlers for cleanup operations
    • Worker processes shut down cleanly with intelligent timeout handling before forced termination
    • Development server reloader implements graceful process shutdown
  • Tests

    • Added integration test to verify shutdown handlers execute correctly during application termination

Review Change Stack

@vercel
Copy link
Copy Markdown

vercel Bot commented May 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
robyn Ready Ready Preview, Comment May 29, 2026 6:21am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

📝 Walkthrough

Walkthrough

This PR adds graceful shutdown support to Robyn so that @app.shutdown_handler runs when the app receives SIGTERM. Worker processes and the dev reloader now register signal handlers that convert SIGTERM to KeyboardInterrupt, wait with a timeout for clean shutdown, and force-kill only if needed. An integration test validates the feature.

Changes

Graceful Shutdown Implementation

Layer / File(s) Summary
Worker process pool graceful shutdown
robyn/processpool.py
GRACEFUL_SHUTDOWN_TIMEOUT constant, _raise_keyboard_interrupt() handler (POSIX only), _terminate_process_pool() helper to terminate and wait, _register_graceful_shutdown_handler() to install SIGTERM→KeyboardInterrupt conversion. Signal handler in run_processes now calls the pool termination helper, and each worker calls the registration function before starting the server.
Reloader graceful shutdown
robyn/reloader.py
GRACEFUL_SHUTDOWN_TIMEOUT constant and EventHandler.wait_for_server_shutdown() method that waits up to the timeout before force-killing. Signal handler, setup_reloader cleanup, and reload() now wait for server shutdown instead of killing immediately. stop_server() wraps terminate() with error handling.
POSIX integration test
integration_tests/test_graceful_shutdown.py
Test skips on Windows. Spawns a Robyn app subprocess with a shutdown handler that writes a sentinel file, waits for server to become reachable via helpers (_get_free_port, _wait_for_server), sends SIGTERM, asserts clean exit (return code 0), and verifies the sentinel file content to confirm the handler ran.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 SIGTERM sent with grace so fine,
Shutdown handlers now align,
Timeout waits before we kill,
Process pools bow to our will,
Tests confirm the deed is done,
Graceful exits, everyone! 🎯

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main objective: implementing graceful shutdown support for SIGTERM, which is the primary focus of all code changes.
Description check ✅ Passed The PR description includes all required template sections: clear issue reference (#1324), comprehensive summary of changes, completed checklist items with appropriate notes, and reasoning for missing documentation updates.
Linked Issues check ✅ Passed The PR fully addresses issue #1324 by implementing SIGTERM handling via signal handlers in processpool.py, graceful shutdown timeouts with force-kill fallback in both processpool.py and reloader.py, and adds a POSIX integration test verifying shutdown handler execution on terminate.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing graceful SIGTERM shutdown: test file adds regression coverage, processpool.py adds signal handling and termination logic, and reloader.py adds graceful shutdown wait logic.

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

📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #1324

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@ahmadmahmoody ahmadmahmoody marked this pull request as ready for review May 29, 2026 06:22
Copy link
Copy Markdown
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.

🧹 Nitpick comments (1)
robyn/reloader.py (1)

112-115: ⚡ Quick win

finally waits without first terminating the subprocess.

Unlike the signal handler (Line 96-97), this cleanup path calls wait_for_server_shutdown() without a preceding stop_server(). If the observer loop exits without the signal handler having run, the server subprocess never receives SIGTERM — it blocks for the full GRACEFUL_SHUTDOWN_TIMEOUT and is then force-killed, skipping graceful shutdown. Sending terminate first is also safe when the process is already reaped (send_signal is a no-op once returncode is set).

♻️ Terminate before waiting
     finally:
         observer.stop()
         observer.join()
+        event_handler.stop_server()
         event_handler.wait_for_server_shutdown()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@robyn/reloader.py` around lines 112 - 115, The finally cleanup currently
calls observer.stop(), observer.join(), then
event_handler.wait_for_server_shutdown() without terminating the child; call the
server termination first to mirror the signal handler: invoke the stop_server()
behavior on the event handler/subprocess (e.g., call event_handler.stop_server()
or otherwise send SIGTERM to the subprocess) before calling
event_handler.wait_for_server_shutdown(), then stop and join the observer; this
ensures the subprocess receives a terminate signal and can perform graceful
shutdown rather than waiting for the timeout.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@robyn/reloader.py`:
- Around line 112-115: The finally cleanup currently calls observer.stop(),
observer.join(), then event_handler.wait_for_server_shutdown() without
terminating the child; call the server termination first to mirror the signal
handler: invoke the stop_server() behavior on the event handler/subprocess
(e.g., call event_handler.stop_server() or otherwise send SIGTERM to the
subprocess) before calling event_handler.wait_for_server_shutdown(), then stop
and join the observer; this ensures the subprocess receives a terminate signal
and can perform graceful shutdown rather than waiting for the timeout.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f00838ae-ddc1-47fa-af5b-ac0ca5ff0139

📥 Commits

Reviewing files that changed from the base of the PR and between 25d4844 and 6fa2210.

📒 Files selected for processing (3)
  • integration_tests/test_graceful_shutdown.py
  • robyn/processpool.py
  • robyn/reloader.py

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.

What is the recommended signal for shutting down the Robyn runtime?

1 participant