fix(cluster): a failed worker start-up says what went wrong and what to try instead (#618) - #635
Merged
Merged
Conversation
…to try instead (#618) When PyBNF could not start its workers on the other machines, the run stopped with Failed to start the dask-ssh cluster (dask-ssh exited with code 1) and, on the cluster this was reported from, nothing else. The real cause was that the login to those machines had failed. No part of the message said so, named a cause, or named a way of running that needs no login -- and since this failure ends the run, that message is the entirety of what the user gets. Stopping is right: carrying on with fewer machines than were asked for would waste the whole run, and the failure is reported within about ten seconds. What was missing is why. The message was empty because the half of dask's output that explains the failure was discarded twice over: * `dask ssh` prints its own account of a refused login -- the node it was connecting to, and the exception paramiko raised -- with `print`, i.e. to *stdout*, and lets only the traceback fall to stderr. PyBNF captured stderr and sent stdout to DEVNULL. * dask ends a failed bring-up with `os._exit(1)`, which does not flush Python's buffers. Its stdout, writing to a file, is block-buffered, and its few hundred bytes never reach the 8 KB that would force a write, so they are dropped at exit. Measured against dask 2026.7.1 on a login that fails: 0 of dask's own lines survived; 15 survive now that both streams are captured into one file and dask is run with PYTHONUNBUFFERED=1. The message now * quotes what dask said, and says plainly when there was nothing to quote rather than falling back to "Check the cluster log directory for details" without naming a directory; * folds the Python traceback frames out of what it quotes. A failed login writes one traceback per node per retry, three retries each, and the sentences that say what happened are buried in dask's and paramiko's own source: 137 captured lines became 32, losing none of those sentences. The log still keeps every line; * says the login is the likely cause when the output reads as a refused credential ("Authentication failed", "No authentication methods available", an encrypted key, a host key that did not match), and says what PyBNF logs in with -- paramiko, which can offer a public key or a typed password and nothing else. That is what makes the failure survive `ssh-keygen`, and makes `ssh othernode hostname` succeeding from the same shell no evidence at all. A machine that could not be reached at all is deliberately not answered that way; * names both ways of running on several machines that need no login, whatever the cause: `cluster_type = slurm-srun` (#614), which starts the workers inside the allocation SLURM already granted, and a `scheduler_file` naming a cluster that is already up. docs/cluster.rst gains a paragraph on what a failed login looks like, and docs/troubleshooting.rst an entry filed under the first line of the message. Verified against a real failed bring-up on this machine (an encrypted ed25519 key, so paramiko raises PasswordRequiredException): the message quotes "SSH reported this exception: Private key file is encrypted" and "SSH connection failed after 3 retries. Exiting.", and carries all three suggestions -- where before the same failure produced only a wall of paramiko traceback. All thirteen new assertions go red against the old code. Full suite green (4492 passed, 25 skipped); docs build clean under -W --keep-going. Signed-off-by: Bill Hlavacek <hlavacek@lanl.gov>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #618.
The problem
A multi-machine run whose workers could not be started stopped with
and, on the cluster this was reported from, nothing else. The real cause was that the login to the other machines had failed. Nothing in the message said so, named a cause, or named a way of running that needs no login — and since the failure ends the run, that message is the entirety of what the user gets.
Stopping is right, and the issue says so: carrying on with fewer machines than were asked for would waste the whole run, and the failure is reported in about ten seconds. What was missing is why.
Why the message was empty
The existing code did include the captured output "when there is any". There was none, and that is not a coincidence — the half of dask's output that explains the failure was discarded twice over:
dask sshexplains itself on stdout. When a login fails itprints the node it was connecting to and the exception paramiko raised, and lets only the traceback fall to stderr. PyBNF captured stderr and sent stdout toDEVNULL.os._exit(1), which does not flush Python's buffers. stdout writing to a file is block-buffered, and dask's few hundred bytes never reach the 8 KB that would force a write, so they are dropped at exit.Measured against dask 2026.7.1 on a login that fails: 0 of dask's own lines survived; 15 survive now that both streams are captured into one file and dask is run with
PYTHONUNBUFFERED=1.What the message says now
ssh-keygen, and makesssh othernode hostnamesucceeding from the same shell no evidence at all. A machine that could not be reached at all is deliberately not answered that way.cluster_type = slurm-srun(Automatic multi-machine startup cannot log in on clusters that use host-based or Kerberos SSH #614), which starts the workers inside the allocation SLURM already granted, and ascheduler_filenaming a cluster that is already up.The same failure, before and after — this is a real bring-up against a host paramiko cannot log in to (an encrypted ed25519 key, so it raises
PasswordRequiredException):Before — 40 lines of paramiko traceback, and nothing else:
After:
Docs
docs/cluster.rstgains a paragraph on what a failed login looks like, in the section that already explains which ways of starting a run log in anywhere.docs/troubleshooting.rstgains an entry filed under the first line of the message, so the text a user sees is searchable.Not in scope
The case where the program PyBNF runs does not exist at all is #615, already fixed: it is refused before anything is launched.
Verification
tests/test_cluster.py, all red against the old code: that stdout is the stream captured, thatPYTHONUNBUFFEREDis set and the rest of the environment passed through, that traceback frames leave the message but stay in the log, that a login failure is named as one, that a network failure is not, that both no-login alternatives are always named, that silence is reported as silence, and that colour escapes are stripped.-W --keep-going.