Reap orphaned R workers on RSPH job cancellation - #51
Conversation
RSPH runs ProctrackType = proctrack/linuxproc with no cgroups, which finds a job's processes by walking parent-PID chains. R's PSOCK workers reparent to init at launch (parallelly uses system(wait = FALSE)), so the chain is severed from the start and scancel never sees them. The step dies, the workers keep running at full CPU, and SLURM schedules new work onto cores they still hold. Cancelling one swfcalib array left 163 orphans on 15 nodes for ~2.5h, about 400 core-hours, and slowed jobs belonging to two other users on the same nodes. scancel sends SIGTERM before SIGKILL (KillWait = 30s), so swf_configs_rsph() now appends a trap to r_loader that reaps R processes carrying this job's SLURM_JOB_ID. Gated behind reap_orphans = TRUE; swf_configs_hyak() is untouched. Also bumps the r_version default from 4.2.1 to 4.5.1, which is what current projects load.
AdrienLeGuillou
left a comment
There was a problem hiding this comment.
The code makes sense and I think it's indeed the right spot to put it.
I haven't used swf_config_rsph in a while and relied on bespoke instructions directly on the "R/hpc_config.R" file on the projects. But for this I think it makes sense to start using it again.
I think we can remove the git section as well. It's not necessary on RSPH
For reference, the Template uses these setup lines:
hpc_node_setup <- c(
". /projects/epimodel/spack/share/spack/setup-env.sh",
"spack unload -a",
"spack load r@4.5.1"
)Just one thing, have you checked the case where only a single array job is canceled? I think the current code is supposed to kill the children only of this array_job, but is it actually the case?
| swf_configs_rsph <- function(partition = "preemptable", | ||
| r_version = "4.2.1", | ||
| r_version = "4.5.1", | ||
| git_version = "2.35.1", |
There was a problem hiding this comment.
I suggest removing this as well as it's not necessary on RSPH
| @@ -104,9 +126,45 @@ swf_configs_rsph <- function(partition = "preemptable", | |||
| paste0("spack load git@", git_version) | |||
There was a problem hiding this comment.
I suggest removing this as well as it's not necessary on RSPH (complement to the argument removal)
PR #52 shipped the same trap as `swf_cleanup_r_workers()`, wired into `swf_configs_rsph()` behind `cleanup_workers`. The bash is byte-identical to this branch's `swf_reap_orphans_lines()`, so the local copy and its `reap_orphans` argument are dropped in favour of the merged, exported one. What is left of this branch is the `r_version` default and Adrien's review. `r_version` moves from 4.2.1 to 4.5.1. The stale default is part of why projects stopped calling this function and hand-rolled their own node setup instead. The vignette example is updated to match. Per review, `swf_configs_rsph()` no longer loads git and the `git_version` argument is gone. `r_loader` is now the three lines the project template uses. This is a breaking change for anyone passing `git_version`, but it fails immediately with "unused argument" when `hpc_configs` is built, not later on the cluster. `swf_configs_hyak()` still loads git and is untouched. Also keeps one point from this branch's docs that main did not have: sweeping orphans up after the fact is a poor substitute for trapping the SIGTERM, because a SIGKILL to a worker mid-write on PanFS can wedge it unkillably in D state, which is the condition `degen_watch.sh` classifies as a filesystem stall. On the review question about a single cancelled array task: verified against NUL-delimited environ fixtures, including the awkward case the #52 doctor work turned up, where exactly one task per array carries SLURM_JOB_ID equal to the ArrayJobId. Cancelling that task matches only itself. Siblings carry the same number as SLURM_ARRAY_JOB_ID, a different record, so `grep -qxz` cannot match them. The same fixtures show what `-x` is for: a four-digit job id without it matches every longer id sharing the prefix, across unrelated arrays.
|
Merged main and acted on both comments. Most of this branch is redundant now. #52 shipped the same trap as git Removed, both the I smoke tested rather than assuming, because git is the system binary, so Two notes. Dropping Single cancelled array task Verified, including the awkward case the doctor work in #52 turned up: exactly one task per array carries Siblings carry that same number, but as The same fixtures show what Also in this merge: |
Adrien, some context since this comes out of nowhere.
RSPH runs
ProctrackType = proctrack/linuxprocwith no cgroups. linuxproc finds a job's processes by walking parent-PID chains, but R's PSOCK workers reparent to init at launch (parallelly usessystem(wait = FALSE)), so SLURM never has a chain to walk.scancelkills the step and the workers keep running at full CPU, invisible to SLURM, which then schedules new work onto cores they still hold.I cancelled one swfcalib array yesterday and left 163 orphaned R processes on 15 nodes for about 2.5 hours, roughly 400 core-hours. They slowed my own India COVID job and two other users' jobs on the same nodes.
scancelsends SIGTERM before SIGKILL (KillWait = 30s), soswf_configs_rsph()now appends a trap tor_loaderthat reaps R processes carrying this job'sSLURM_JOB_ID. A/B tested on RSPH: cancelling a 4-worker job left 4 orphans without the trap and 0 with it. Gated onreap_orphans = TRUE, andswf_configs_hyak()is untouched.Why here rather than in slurmworkflow: this is an RSPH misconfiguration workaround, not general behavior. On a properly configured cgroup cluster the trap would convert a graceful SIGTERM (where R can flush and save) into an immediate
kill -9, which is strictly worse for no benefit.swf_configs_rsph()exists to hold RSPH-specific bash, so it seemed like the right layer.Three details in the trap are load-bearing, and each one was wrong in my first draft, so they are worth a look:
grep -qxz, not-qz. Unanchored,SLURM_JOB_ID=4135matches41352064./proc/<pid>/environis permission-gated across users but not across your own concurrent jobs, so nothing else prevents that.SLURM_JOB_IDguard. An empty variable degrades the pattern toSLURM_JOB_ID=, which matches every R process you own.:. The generatedjob.shrunsset -e, and the loop otherwise returns the last grep's status. On a normal exit this job's workers are already gone, so that grep fails against some other job's R process and the step exits non-zero despite having succeeded.SLURM_JOB_IDis unique per array task (checked: tasks 42/43/44 carried 41352064/65/66 while sharingSLURM_ARRAY_JOB_ID), so a cancelled task never touches its siblings.Also bumps the
r_versiondefault from 4.2.1 to 4.5.1. As far as I can tell only PrEP-DistCEA calls this function today and everything else hand-rolls its ownhpc_node_setup, which I suspect is partly because the default went stale.Unrelated, but spotted while in there:
swf_configs_hyak()'s own defaults (hpc = "klone", partition = "csde") error out, since klone wants "compute" or "ckpt". Left alone here.The real fix is RSPH running
proctrack/cgroup, which I have emailed them about. This is mitigation until then.