From d59323a475f5f8d7f5ec6a78ac231a89c9458205 Mon Sep 17 00:00:00 2001 From: gmega Date: Thu, 19 Jun 2025 10:57:13 -0300 Subject: [PATCH] feat: allow PID to be excluded from process group (sort of) --- src/procmon.bash | 46 ++++++++++++++++++++++++++++++++++++++++++ test/test_procmon.bats | 28 ++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/procmon.bash b/src/procmon.bash index d62b837..79f413b 100644 --- a/src/procmon.bash +++ b/src/procmon.bash @@ -14,6 +14,10 @@ _pm_init_output() { mkdir -p "${_pm_output}" } +# Starts the process monitor. +# Returns: +# 1 if the process monitor is already running +# 0 otherwise pm_start() { _pm_assert_state_not "running" || return 1 _pm_init_output @@ -53,6 +57,17 @@ pm_start() { return 0 } +# Tracks the last job started by the shell as part of a monitored group. +# If a tracked process dies: +# 1. without an error code (e.g. it is killed); +# 2. with a non-zero error code. +# Then all processes in the process group and their descendants (with caveats) +# are also killed. This makes sure that the harness does not leave processes +# behind. +# +# Returns: +# 1 if the process monitor is not running +# 0 otherwise pm_track_last_job() { _pm_assert_state "running" || return 1 @@ -62,6 +77,29 @@ pm_track_last_job() { fi } +# Stops tracking a given PID. +# Arguments: +# $1: PID to stop tracking +# Returns: +# 1 if the process monitor is not running +# 0 otherwise +# Note: +# This function is flaky. The process monitor +# might still see the PID as tracked after this +# function returns for a short period of time, +# so do not rely too much on it. +pm_stop_tracking() { + _pm_assert_state "running" || return 1 + + local pid=$1 + rm -rf "${_pm_output}/${pid}.pid" || true +} + +# Returns the list of PIDs being tracked by the process monitor by +# setting the `result` variable. +# Returns: +# 1 if the process monitor is not running +# 0 otherwise pm_known_pids() { _pm_assert_state "running" || return 1 @@ -114,10 +152,18 @@ _pm_halt() { pm_kill_rec "$_pm_pid" } +# Stops the process monitor, killing the entire process group. +# Returns: +# 1 if the process monitor is not running +# 0 otherwise pm_stop() { _pm_halt "halted" } +pm_join() { + await "$_pm_pid" "$1" +} + pm_job_exit() { exit_code=$1 echo "$exit_code" > "${_pm_output}/${BASHPID}.pid" diff --git a/test/test_procmon.bats b/test/test_procmon.bats index 5029d22..eae54b8 100644 --- a/test/test_procmon.bats +++ b/test/test_procmon.bats @@ -130,7 +130,33 @@ setup() { await "$p1" await "$p2" - sleep 1 + pm_join 3 assert_equal $(pm_state) "halted_process_failure" +} + +@test "should no longer track a process if requested" { + assert pm_start + + ( + while true; do + sleep 1 + done + pm_job_exit 1 + ) & + pid1=$! + pm_track_last_job + + pm_stop_tracking $pid1 + kill -SIGKILL $pid1 + await "$pid1" + + # Again, we need to allow time for the procmon + # to pick up on the kill. + sleep 1 + + pm_stop + pm_join 3 + + assert_equal $(pm_state) "halted" } \ No newline at end of file