refactor: extract subprocess cleanup into shared subproc helper (#210)

bird_x.py and youtube_yt.py had four near-identical copies of the same
subprocess cleanup dance (Popen + os.setsid + communicate(timeout) +
SIGTERM via killpg + proc.kill() fallback + wait(5)). Extract to
lib.subproc.run_with_timeout(), which:

- runs the child in its own process group via os.setsid where available
- raises SubprocTimeout on timeout
- on timeout: SIGTERM the group, fall back to proc.kill(), wait up to 5s
- accepts an on_pid callback so bird_x can still register child PIDs
  with last30days.register_child_pid for whole-process cleanup
- captures stdout/stderr as strings in a SubprocResult dataclass

Migrated call sites: _run_bird_search, search_handles inner worker,
search_youtube, fetch_transcript. With the helper in place, the signal
and subprocess imports became dead in both files (plus os in
youtube_yt) and went with them.

Tests: 9 new subproc tests cover success, non-zero exit, stderr capture,
timeout-raises, timeout-kills-group, missing-command, env passthrough,
PID callback, and callback-exception suppression. test_env_v3 and
test_youtube_yt patch subproc.run_with_timeout instead of the removed
bird_x.subprocess and yt-dlp subprocess.
This commit is contained in:
Ilia Alshanetsky
2026-04-25 17:17:47 -04:00
committed by GitHub
parent 2acbf8a869
commit bbf892aecc
6 changed files with 268 additions and 130 deletions
+4 -1
View File
@@ -30,8 +30,11 @@ class EnvV3Tests(unittest.TestCase):
self.assertEqual("b", bird_x._credentials["CT0"])
def test_bird_auth_never_checks_browser_cookies(self):
# The guarantee: is_bird_authenticated() must not spawn any child
# process to probe for cookies. All subprocess paths in bird_x go
# through subproc.run_with_timeout, so patching that covers it.
with mock.patch("lib.bird_x.is_bird_installed", return_value=True), mock.patch(
"lib.bird_x.subprocess.run",
"lib.bird_x.subproc.run_with_timeout",
side_effect=AssertionError("browser-cookie whoami should not run"),
):
bird_x._credentials.clear()