Skip to content

test infra: abort watchdog so a deadlocked run cannot survive Jenkins abort and hold the device hub#15422

Open
Nir-Az wants to merge 3 commits into
realsenseai:developmentfrom
Nir-Az:pytest-abort-watchdog
Open

test infra: abort watchdog so a deadlocked run cannot survive Jenkins abort and hold the device hub#15422
Nir-Az wants to merge 3 commits into
realsenseai:developmentfrom
Nir-Az:pytest-abort-watchdog

Conversation

@Nir-Az

@Nir-Az Nir-Az commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Overview: A Jenkins abort can no longer leave an immortal test process holding the Acroname hub: a small watchdog process guarantees the run dies even when it is deadlocked in native code.

Tracked on [RSDSO-21761]

Why

On 2026-07-14 every Linux CI agent started failing Acroname connect with result=25 (aErrConnection). Root cause: aborted builds left orphaned pytest processes deadlocked in native code (mutex/GIL AB-BA). The Python SIGTERM handler in rspy.signals can never run in that state — signal handlers only execute when the main thread returns to the interpreter loop — so the process survived the abort (Jenkins: "After 20s process did not stop") and kept the BrainStem USB link open, failing every subsequent build on the agent.

Summary

  • rspy/signals.py: register_signal_handlers() now also starts a GIL-independent watchdog subprocess (POSIX only). It receives the same SIGTERM/SIGINT as the run (same process tree), waits a 60s grace period so normal cleanup can finish, then SIGKILLs the run if it is still alive. The kernel then closes all fds, releasing the hub.
  • Readiness handshake so the watchdog's handlers are armed before the run proceeds; exits on its own (stdin EOF) when the run ends normally.
  • New infra tests (infra-tests/test_signals_watchdog.py): stuck-cleanup gets SIGKILLed, clean abort exits normally without watchdog interference, no watchdog process leaks after normal exit.

Test plan

  • infra-tests/test_signals_watchdog.py — 3 tests pass on Linux (validated locally)
  • Full infra-tests suite on Windows — 148 passed, 12 skipped (watchdog is a no-op on Windows)
  • LibCI run

🤖 Generated by AI

…run cannot survive Jenkins abort and hold the device hub

@sysrsbuild-gh-agentic sysrsbuild-gh-agentic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⚙️ Auto-generated review by rs-agentic bot

Comment thread unit-tests/py/rspy/signals.py Outdated
Comment thread unit-tests/py/rspy/signals.py
Comment thread unit-tests/infra-tests/test_signals_watchdog.py
Comment thread unit-tests/py/rspy/signals.py
@Nir-Az
Nir-Az requested a review from AviaAv July 15, 2026 11:53

@AviaAv AviaAv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good, some small comments

'''


def start_abort_watchdog(grace=None):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

unused parameter, the tests override WATCHDOG_GRACE_S directly

def wait_for_ready(proc, timeout=20):
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
line = proc.stdout.readline()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

readline is blocking, so this wouldn't work here - let's use timeout(60) at the top of the file instead and simplify wait_for_ready

already running). See _WATCHDOG_CODE above for why it must be a separate process.
"""
global _watchdog
if os.name != 'posix' or _watchdog is not None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

_watchdog is never reset, let's use

if os.name != 'posix' or (_watchdog is not None and _watchdog.poll() is None):

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