Skip to content

Spare localusers from the epilog's killall - #1403

Closed
100milliongold wants to merge 3 commits into
NVIDIA:masterfrom
xiilab:fix/epilog-40-spare-localusers
Closed

Spare localusers from the epilog's killall#1403
100milliongold wants to merge 3 commits into
NVIDIA:masterfrom
xiilab:fix/epilog-40-spare-localusers

Conversation

@100milliongold

Copy link
Copy Markdown
Contributor

What happens

epilog.d/41-lastuserjob-ssh checks /etc/slurm/localusers.backup before it touches a user:

if grep -q -w "$SLURM_JOB_USER" /etc/slurm/localusers.backup ; then
    exit 0  # don't revoke access for these users
fi

epilog.d/40-lastuserjob-processes does not, even though it is the broader of the two:

if [ "$SLURM_JOB_USER" != root ]; then
    if killall -9 -u "$SLURM_JOB_USER" ; then

killall -9 -u reaps every process the user owns on that node. That includes an interactive login shell and the sshd session carrying it.

So an operator listed in localusers.backup — listed there precisely so their access is not cut off — is disconnected the moment their job's epilog runs.

How we hit it

Two-node cluster where the controller is also a compute node, so the account submitting jobs also has a login shell on a node that runs the epilog.

11:09:55  Running /etc/slurm/epilog.d/41-lastuserjob-ssh ...
11:09:55  Running /etc/slurm/epilog.d/42-lastuserjob-cleanup ...
11:10:03  epilog for JobId=2 ran for 8 seconds

and on the operator's terminal, at the same moment:

Connection to <node> closed by remote host.

40 leaves no log line of its own here, because its logger call is inside if killall ...; then and killall takes down the shell that would have carried the message.

There is a second, downstream symptom. Node Health Check runs check_ps_service -u root -d sshd sshd; on a socket-activated sshd (Ubuntu 24.04) there is no persistent sshd process once the last session dies, so NHC failed 2.5 minutes later:

error: health_check failed: rc:1 output:ERROR: nhc: Health check failed:
       check_ps_service: Service sshd (process sshd) owned by root not running

That NHC check is arguably wrong on its own for socket activation, and is not addressed here.

The change

Apply the guard 41 already uses, unchanged in form.

Cleanup behaviour is identical for every user not in localusers.backup. And with ProctrackType=proctrack/cgroup the job's own processes are already reaped by Slurm, so this script is a sweep for strays that escaped the cgroup rather than the primary teardown — skipping it for a handful of operator accounts does not leave the node dirty.

Verified

Applied to both nodes of the cluster above:

guard fires for ubuntu (in localusers.backup)  : YES — killall skipped
guard fires for an ordinary user               : no  — killall still runs

Not changed

42-lastuserjob-cleanup has the same asymmetry — it removes the user's files under /tmp and /dev/shm with no exemption check. Its blast radius is much smaller than killall -9, so it is left alone here rather than widened into this PR. Happy to follow up if you would rather the three scripts agree.

…and recent Slurm

The exclusive-job check in run-parts.sh had two independent failures:

1. It called scontrol/squeue through PATH. slurmd's environment does not
   include a custom slurm_install_prefix, so both commands produced empty
   output, numcpus_sys and numcpus_job were both "", the comparison was
   true, and every job ran the *-exclusive-* prolog/epilog scripts. On a
   shared node this reset power limits and clocks on all GPUs and dropped
   page caches for every job.

2. It parsed "scontrol show job" with grep -Eio "TRES=cpu=[0-9]+". On
   recent Slurm the output has both ReqTRES= and AllocTRES= lines, so the
   pattern matched twice and numcpus_job became a multi-line value that
   never compared equal. With scontrol on PATH, exclusive jobs were
   therefore never detected.

Use {{ slurm_install_prefix }}/bin/squeue by absolute path (the file is
already deployed via the template module) and read allocated CPUs and node
count with -o %C / -o %D instead of parsing scontrol. Guard against an
empty result so a lookup failure means "not exclusive" rather than
"exclusive".

Observed on DGX OS 7.5.0, Slurm 26.05.1, slurm_install_prefix=/raid/slurm/usr/local:
- before the binaries were symlinked into /usr/local/bin, every srun
  --gres=gpu:1 job logged "Running .../50-exclusive-gpu" and prolog took
  6-8 s (srun: Prolog hung on node);
- after symlinking, a bash -x run of the script showed numcpus_job='1<nl>56'.

Signed-off-by: Jea-Eok-Kim <je.kim@xiilab.com>
This script runs with "set -e", so a bare `numcpus_job=$(squeue ...)`
assignment aborts the entire prolog/epilog run when squeue exits nonzero.
Redirecting stderr does not suppress the exit status. The result is that a
transient squeue failure skips every part script, not just the exclusive ones,
and the job fails.

Both allocation fields are now fetched by a single `-o "%C %D"` call inside an
`if` condition, so the failure is visible and handled. A failed, empty or
non-numeric lookup leaves exclusive=0 and logs a warning.

The same reasoning is applied to the last-user-job count, with one difference:
piping squeue into `wc -l` hides its exit status, and a failed lookup would be
read as "no other jobs" and run the *-lastuserjob-* cleanup scripts while
another job of the same user is still on the node. A failed lookup now leaves
last_user_job=0.

Decision matrix, verified on a DGX B300 (Ubuntu 24.04, bash 5.2, 256 CPUs) by
substituting a squeue stub that honours the -o format and the -j flag:

  case        before                          after
  ----------  ------------------------------  ------------------------------
  exclusive   rc=0  all three parts ran       rc=0  all three parts ran
  shared      rc=0  exclusive part skipped    rc=0  exclusive part skipped
  otherjobs   rc=0  lastuserjob part skipped  rc=0  lastuserjob part skipped
  empty       rc=0  silently non-exclusive    rc=0  non-exclusive + 1 warning
  nonnumeric  rc=0  silently non-exclusive    rc=0  non-exclusive + 1 warning
  fail        rc=1  NO part ran at all        rc=0  normal part ran + 2 warnings

The three normal cases are unchanged, so there is no regression; the failure
cases stop taking the whole run down and stop deciding silently.
41-lastuserjob-ssh checks /etc/slurm/localusers.backup before it touches a
user, so operator accounts keep their access when a job ends.
40-lastuserjob-processes does not, yet it is the broader of the two: `killall
-9 -u` reaps every process the user owns on the node, including an interactive
login shell and its sshd session.

An operator who submits from a login shell on a compute node is therefore
disconnected the instant the epilog runs, even though that account is listed
in localusers.backup precisely so it will not be cut off. Observed on a
two-node cluster where the controller is also a compute node.

Apply the same guard 41 already uses. Job cleanup is unchanged for every user
not in that file, and with ProctrackType=proctrack/cgroup the job's own
processes are already reaped by Slurm, so this script is a sweep for strays
rather than the primary teardown.

42-lastuserjob-cleanup has the same asymmetry -- it deletes the user's files
under /tmp and /dev/shm with no exemption check -- but its blast radius is
much smaller, so it is left alone here.
@100milliongold

Copy link
Copy Markdown
Contributor Author

Closing in favour of #1404. This branch was cut from a topic branch rather than master, so it carried the two commits already under review in #1391. #1404 has the same change on its own, one file, +12/-0.

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.

2 participants