Skip to content

fix: Exit 0 on SIGTERM while serving - #430

Merged
marianogappa merged 3 commits into
mainfrom
fix/exit-0-on-sigterm
Nov 3, 2026
Merged

marianogappa merged 3 commits into
mainfrom
fix/exit-0-on-sigterm

Conversation

@marianogappa

@marianogappa marianogappa commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Stop the gRPC server from a background thread on SIGTERM and SIGINT, and force exit 0 if a blocked RPC handler keeps the process alive past the grace period or a second signal arrives, so a plugin always exits 0 on shutdown (fixes cloudquery/cloudquery#23716).

@marianogappa
marianogappa marked this pull request as ready for review November 3, 2026 07:44
@marianogappa
marianogappa requested a review from a team as a code owner November 3, 2026 07:44
@marianogappa
marianogappa requested review from erezrokah and removed request for a team November 3, 2026 07:44

@erezrokah erezrokah left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The idle-server shutdown and lock-restoration tests pass, but the new signal handling can leave the process alive indefinitely when an RPC handler is blocked. Please add bounded shutdown behavior for this case before merging.

Comment thread cloudquery/sdk/serve/plugin.py Outdated
Comment on lines +415 to +416
def _stop_in_background(self, signum, frame):
threading.Thread(target=self.stop, daemon=True).start()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P2] Bound process shutdown when an RPC handler is blocked

Routing SIGTERM exclusively through stop() can leave the plugin process alive indefinitely. server.stop(5) cancels the RPC but does not interrupt the Python handler; after run() returns, interpreter shutdown waits for the executor worker. With Init blocked on threading.Event().wait(), the base process terminated immediately on SIGTERM, while this head remained alive beyond the five-second grace period and ignored further SIGTERM/SIGINT signals because they only stopped the already-stopped server. A plugin blocked on upstream I/O can therefore require SIGKILL instead of exiting cleanly. Add a bounded shutdown path for active handlers and a subprocess regression test covering this case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in afba537

@erezrokah erezrokah left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The blocked-RPC case now exits successfully within the intended bound. However, synchronous stream flushing can block or raise before the forced exit occurs. Please make forced termination independent of stdout/stderr flushing.

Comment thread cloudquery/sdk/serve/plugin.py Outdated
Comment on lines +435 to +436
sys.stdout.flush()
sys.stderr.flush()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P2] Keep stream flushing from preventing forced exit

Both forced-exit paths call these synchronous flushes before reaching os._exit(0). If a worker is blocked writing to a full stdout pipe, flush() waits on that buffered stream, so both the timeout thread and the second-signal handler hang. I reproduced the process remaining alive after 12 seconds and a second signal, with both stacks stopped at line 435. Closing the pipe instead caused BrokenPipeError in the shutdown thread, and a second signal produced exit code 120. Make flushing best-effort and bounded so neither blocking I/O nor a flush exception can prevent os._exit(0), and cover full/closed output pipes in the subprocess tests.

@marianogappa
marianogappa merged commit b733e64 into main Nov 3, 2026
7 checks passed
@marianogappa
marianogappa deleted the fix/exit-0-on-sigterm branch November 3, 2026 12:38
kodiakhq Bot pushed a commit that referenced this pull request Nov 3, 2026
🤖 I have created a release *beep* *boop*
---


## [0.1.59](v0.1.58...v0.1.59) (2026-11-03)


### Bug Fixes

* Exit 0 on SIGTERM while serving ([#430](#430)) ([b733e64](b733e64))

---
This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
@jpollard-cs

Copy link
Copy Markdown
Contributor

Looks good, thanks!!

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.

bug: plugin-sdk-python: sys.exit() from a SIGTERM handler can exit 1 during wait_for_termination

3 participants