5 Commits

Author SHA1 Message Date
Tanmoy Sarkar
672c45a9c7 fix(companion): Harden manager and control against runtime errors
- _safe_kill: a companion can exit between the manager deciding to
  signal it and the kill landing; swallow ProcessLookupError at the three
  os.kill sites so the resulting race cannot take the manager down.
- _redirect_output: close the opened log fd after dup2 so a long-lived
  companion does not leak a descriptor per start.
- serve_connection: drop a control connection whose line grows past
  MAX_LINE_BYTES without a newline, so a client cannot pin unbounded
  memory in the manager.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 23:30:48 +05:30
Tanmoy Sarkar
3b972fe310 fix(companion): Validate stop_signal and harden control dispatch
A typo'd companion_stop_signal (e.g. "SIGTRM") passed validate_string
but raised ValueError in _signal_number when the manager later tried to
send it -- propagating past handle_line and killing the run loop.

Validate stop_signal at config-build time so a bad value fails loudly
on load and reread. As defense-in-depth, catch unexpected exceptions in
ControlServer.handle_line so no handler bug can escape and kill the
manager; they now return an error envelope.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 23:25:26 +05:30
Tanmoy Sarkar
4d554c2fac test(companion): Add control command tests
Wire ControlServer.handle_line to a real CompanionManager.handle_command and
assert the full decode/dispatch/encode round trip: status returns the companion
list, start routes through and reports a message, and unknown command, missing
name, and reread without a loader each return an error envelope.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 23:05:41 +05:30
Tanmoy Sarkar
9f3762d6b6 refactor(companion): Spell out abbreviated identifiers
No behaviour change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 22:03:13 +05:30
Tanmoy Sarkar
104bfcebdd feat(companion): Add Unix control socket and JSON command protocol
Add gunicorn/companion/control.py with ControlServer, the manager's control
endpoint. It owns the Unix socket lifecycle (create unlinks any stale socket,
binds, chmods 0o600, and listens; close cleans up) and the newline-delimited
JSON framing: serve_connection buffers reads and answers each complete line.
decode_command parses a request into a JSON object carrying a string cmd, and
encode_response writes a newline-terminated JSON line; malformed input becomes
a CommandError rendered as an {ok: false, error: ...} reply so a bad client
can't take the manager down. Turning a command into an action is delegated to a
dispatch callable, wired up in the later command tasks.

The socket is 0o600 and owned by the non-root user gunicorn runs as; no group
switching.

Add tests/test_companion_control.py covering decode, encode, handle_line
dispatch and error envelopes, and socket create/close.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 18:23:03 +05:30