-
Notifications
You must be signed in to change notification settings - Fork 88
fix(clp-package): Forward signals for cleanup of native containers in sbin scripts (fixes #1701).
#1716
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?
fix(clp-package): Forward signals for cleanup of native containers in sbin scripts (fixes #1701).
#1716
Changes from all commits
0bdf25d
22ad6e3
e2ce3ea
d33b7c9
faa192a
3bc97f2
eacea2b
a5c1029
d706cbe
1bd2f05
40b41f4
732015f
5938bbe
e816119
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,10 +5,12 @@ | |
| import pathlib | ||
| import re | ||
| import secrets | ||
| import signal | ||
| import socket | ||
| import subprocess | ||
| import uuid | ||
| from enum import auto | ||
| from typing import Any | ||
|
|
||
| import yaml | ||
| from clp_py_utils.clp_config import ( | ||
|
|
@@ -50,6 +52,8 @@ | |
| EXTRACT_JSON_CMD = "j" | ||
|
|
||
| DOCKER_MOUNT_TYPE_STRINGS = ["bind"] | ||
| DOCKER_RUN_DEFAULT_TIMEOUT = 10 | ||
| DOCKER_RUN_TERMINATE_GRACE_FACTOR = 1.5 | ||
|
|
||
| S3_KEY_PREFIX_COMPRESSION = "s3-key-prefix" | ||
| S3_OBJECT_COMPRESSION = "s3-object" | ||
|
|
@@ -761,6 +765,51 @@ def get_celery_connection_env_vars_list(container_clp_config: ClpConfig) -> list | |
| return env_vars | ||
|
|
||
|
|
||
| def run_subprocess_with_signal_forward(*args: Any, **kwargs: Any) -> subprocess.CompletedProcess: | ||
| """ | ||
| Runs a subprocess in its own process group and forwards `SIGINT` and `SIGTERM` received on the | ||
| calling process to the entire child process group. | ||
|
|
||
| :return: The completed subprocess. | ||
| """ | ||
| kwargs["start_new_session"] = True | ||
| proc = subprocess.Popen(*args, **kwargs) | ||
|
|
||
| # Save original handlers | ||
| original_handlers = { | ||
| signal.SIGINT: signal.signal(signal.SIGINT, signal.SIG_DFL), | ||
| signal.SIGTERM: signal.signal(signal.SIGTERM, signal.SIG_DFL), | ||
| } | ||
|
|
||
| def _signal_handler(signum: int, frame: Any) -> None: | ||
| """Run cleanup, then invoke the original handler.""" | ||
| try: | ||
| # Terminate the entire process group to cascade termination to all children. | ||
| pgid = os.getpgid(proc.pid) | ||
| os.killpg(pgid, signum) | ||
| except ProcessLookupError: | ||
| # Child already terminated. | ||
| pass | ||
|
|
||
| # Restore and invoke original handler | ||
| original = original_handlers[signal.Signals(signum)] | ||
| signal.signal(signum, original) | ||
| if original not in (signal.SIG_DFL, signal.SIG_IGN): | ||
| original(signum, frame) | ||
|
|
||
| signal.signal(signal.SIGINT, _signal_handler) | ||
| signal.signal(signal.SIGTERM, _signal_handler) | ||
|
|
||
| try: | ||
| proc.wait() | ||
| finally: | ||
| # Restore original handlers | ||
| for sig, handler in original_handlers.items(): | ||
| signal.signal(sig, handler) | ||
|
|
||
| return subprocess.CompletedProcess(proc.args, proc.returncode) | ||
|
Comment on lines
+803
to
+810
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: fd "general.py" --path "*/clp-package-utils/*" -x cat -n {}Repository: y-scope/clp Length of output: 270 🏁 Script executed: fd "general.py" -p "*/clp-package-utils/*" -x cat -n {}Repository: y-scope/clp Length of output: 163 🏁 Script executed: find . -name "general.py" -type f | head -20Repository: y-scope/clp Length of output: 114 🏁 Script executed: cat -n ./components/clp-package-utils/clp_package_utils/general.py | sed -n '790,820p'Repository: y-scope/clp Length of output: 1317 🏁 Script executed: cat -n ./components/clp-package-utils/clp_package_utils/general.py | sed -n '750,815p'Repository: y-scope/clp Length of output: 2939 🏁 Script executed: python - <<'PY'
import ast
from pathlib import Path
risky_kwargs = {"stdout", "stderr", "capture_output", "timeout", "input", "check", "text", "encoding", "errors"}
for path in Path(".").rglob("*.py"):
try:
src = path.read_text(encoding="utf-8")
tree = ast.parse(src)
except Exception:
continue
for node in ast.walk(tree):
if isinstance(node, ast.Call):
fn = node.func
name = fn.id if isinstance(fn, ast.Name) else fn.attr if isinstance(fn, ast.Attribute) else None
if name == "run_subprocess_with_signal_forward":
used = {kw.arg for kw in node.keywords if kw.arg is not None}
overlap = sorted(used & risky_kwargs)
if overlap:
print(f"{path}:{node.lineno}: kwargs={overlap}")
PYRepository: y-scope/clp Length of output: 139 🏁 Script executed: cat -n ./components/clp-package-utils/clp_package_utils/controller.py | sed -n '1145,1165p'Repository: y-scope/clp Length of output: 847 🏁 Script executed: rg "run_subprocess_with_signal_forward" --type py -B 2 -A 2Repository: y-scope/clp Length of output: 9250 Replace The current implementation at line 804 uses Recommended fix try:
- proc.wait()
+ stdout, stderr = proc.communicate()
finally:
# Restore original handlers
for sig, handler in original_handlers.items():
signal.signal(sig, handler)
- return subprocess.CompletedProcess(proc.args, proc.returncode)
+ return subprocess.CompletedProcess(proc.args, proc.returncode, stdout, stderr)🤖 Prompt for AI Agents |
||
|
|
||
|
Comment on lines
+768
to
+811
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Signal‑forwarding helper is solid but tightly coupled to main thread and global handlers The overall behaviour of There are a couple of important constraints worth making explicit or guarding against:
If the intent is “CLI‑only, main‑thread‑only” usage, tightening the documentation (and optionally adding a defensive main‑thread assertion) should be enough. 🤖 Prompt for AI Agents |
||
|
|
||
| def _is_docker_compose_project_running(project_name: str) -> bool: | ||
| """ | ||
| Checks if a Docker Compose project is running. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.