Skip to content

fix(cli): delete dev endpoints when rp flash dev is stopped by a signal - #625

Open
justinwlin wants to merge 5 commits into
mainfrom
fix/dev-signal-cleanup
Open

justinwlin wants to merge 5 commits into
mainfrom
fix/dev-signal-cleanup

Conversation

@justinwlin

@justinwlin justinwlin commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Flash → SDK 2.0 fix stack

PRs 1–9 are independent PRs off main, and 10 is stacked on 9. Merge in this order. Hover a link for its status.

# PR Fixes Needs
1 #627 ✅ merged: CI e2e fixed on main —
2 #623 GPU crash loop: torch pulled in by peft/accelerate gets bundled nothing
3 #624 Workers land on GPUs you didn't request (whole pools) nothing
4 👉 #625 (this PR) rp flash dev leaks endpoints when stopped by a signal nothing
5 #626 vol.path says "not mounted" on deployed workers nothing
6 #628 Account API key copied into every endpoint nothing
7 — Release runpod 2.0.0rc2 with 1–6 1–6 merged
8 runpod-workers/sdk-runtime#1 Rebuild runtime images (dev sessions run an old SDK) independent
9 runpod/runpod-plugins-official#89 Plugin docs for the fixed SDK (flash lane → SDK 2.0) 2.0.0rc2 on PyPI
10 runpod/runpod-plugins-official#90 Plugin VolumeCache guidance: global volumes, measured numbers, golden paths 08/09 (stacked on #89) runpod/runpod-plugins-official#89

Stopping rp flash dev with a signal left its endpoints running and billing. An agent's dev loop hits this every time: it starts the session in the background and stops it with kill.

Cause

  • Non-interactive shells start background jobs with SIGINT ignored. Python then never installs its KeyboardInterrupt handler, and asyncio doesn't add its own, so kill -INT does nothing.
  • SIGTERM has no handler, so the process dies without running teardown.
  • A stop during a long entrypoint call waits for that call with no limit.

runpod-flash cleaned up on KeyboardInterrupt in its CLI.

Change

  • Install SIGINT and SIGTERM handlers on the session loop. The first signal cancels the session, and teardown deletes the endpoints. A second signal stops waiting for a running entrypoint.
  • Once teardown starts, signals are ignored so every delete finishes. An endpoint whose create was in flight is recorded and deleted.
  • The entrypoint thread is a daemon, and it ignores the loop closing underneath it after a forced stop.

Testing

rp flash dev main.py < /dev/null &, then stop it main runpod-flash 1.21 This PR
kill -TERM process dies, endpoint left running not tested ✓ cleaning up 1 deleted
kill -INT from a script (SIGINT ignored at start) nothing happens not tested (cleans up on KeyboardInterrupt) ✓ cleaning up 1 deleted
Signal while endpoints are being deleted n/a n/a the remaining deletes finish (unit test)
Signal while an endpoint is being created new endpoint untracked and left n/a the created endpoint is recorded and deleted (unit test)
How we tested and compared
  • Live. We ran the dev loop the way an agent does: start rp flash dev in the background from a non-interactive script, wait for the first done in, then send the signal. We checked the session log for the cleanup line and listed the account's endpoints afterward. We ran this before the review fixes and again on a build of all five PRs on the current main. Both runs cleaned up after TERM and after INT, with no dev-* endpoint left.
  • Before. On main, the plugin's dev loop needed a wrapper that restored SIGINT before exec, because a plain kill left the endpoint billing.
  • Unit tests (all pass three runs in a row): SIGTERM deletes endpoints, SIGINT cleans up even when ignored at start, a second signal tears down before a blocked entrypoint returns, a signal during teardown still deletes, and a signal during a create deletes the new endpoint. pytest tests: 1484 passed, coverage 91.65%.
Review (Codex, two rounds)
Finding Resolution
A signal during teardown cancelled the deletes still pending Fixed in 0e88f57. Signals are ignored once teardown starts.
A signal during save_endpoint lost the id the platform had already created Fixed in 0e88f57. _create_tracked shields the create, waits for the reply, and records the endpoint before re-raising.
No test with several endpoints pending deletion Open. The teardown loop is unchanged, and the new test covers the cancel path with one endpoint.

Comment thread runpod/rp_cli/main.py Fixed
Comment thread runpod/rp_cli/main.py Fixed
A dev session started in the background from a non-interactive shell
inherits SIGINT as ignored, so Python never raises KeyboardInterrupt,
and SIGTERM has no handler. Either way the process stopped without its
teardown and left the session's endpoints running and billing. A stop
during a long entrypoint call also waited for that call indefinitely.

Install SIGINT and SIGTERM handlers on the session loop. The first
signal cancels the session, which runs teardown and deletes the
endpoints. A second signal stops waiting for a running entrypoint. The
entrypoint thread is a daemon and tolerates the loop closing under it.
…ignal

a signal during teardown cancelled the deletes still pending, and a signal during endpoint creation dropped the id the platform had already assigned. teardown now ignores signals, and a cancelled create waits for its reply and records the endpoint so teardown deletes it.
@justinwlin
justinwlin force-pushed the fix/dev-signal-cleanup branch from 79f63fb to 0e88f57 Compare October 20, 2026 15:48
@justinwlin
justinwlin marked this pull request as ready for review October 20, 2026 15:48
Comment thread runpod/rp_cli/main.py Fixed
Comment thread runpod/rp_cli/main.py Fixed
Comment thread runpod/rp_cli/main.py Fixed
Comment thread runpod/rp_cli/main.py Fixed

This branch has not been deployed

No deployments
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