Repository navigation
Conversation
cobo_comm.c had nearly-identical copies of cobo.c's read and write helpers, which differed only in that a bug fix had been applied to one but not the other. Remove the duplicates.
cobo_open looked up each setting with a trailing newline in its name, so none was ever read, and the FE side, cobo_server_open, did not read them at all.
Every read during tree formation waited without a limit, so a dead or stalled peer would stall startup forever. The parent's accept() and the tree reads are now bounded by COBO_CONNECT_TIMELIMIT.
cobo_open ignored a failed cobo_open_tree and proceeded into the barrier, which would then hang forever. Failures in tree formation, barrier, allreduce and broadcast, cachepath consensus and the ready ack are now propagated to the caller, and the server returns an error instead of calling exit() when the tree can't be opened.
spindleInitFE ignored failures during tree formation and proceeded to try to read the root's ack, continue with startup, and returned 0. Failures now cause spindleInitFE to return -1. Broadcasts no longer raise SIGPIPE, so a dead root doesn't kill the FE. The FE now waits for ALIVE before sending the preload list, so preload time doesn't count against the limit.
After a failed forward to a child communication would also be interrupted with the parent, resulting in a hang at exit.
The client polled for the server's ready file for 60 seconds, but then fell through into blocking FIFO opens. Instead of hard-coding 60 seconds, the client now waits SPINDLE_READY_TIMELIMIT seconds. On failure, client closes and removed FIFOs; server checks for dead client by ENXIO.
If the rankinfo or cachepath query failed, spindle_bootstrap silently continued and ran the application without Spindle. It now exits nonzero.
The server opened a client's input FIFO with a blocking open(), so a client that died mid-connect stalled the whole server. The open is now nonblocking and the connection is dropped on ENXIO.
--start-session reported success even when the session daemon exited before finishing startup.
spindle now shuts down the launcher when spindleInitFE fails, instead of returning with its daemons still running.
Add fault injection points which cause an exit, delay, or hang at points during startup set by environment variables. Add a startup fault testsuite which confirms that exits or hangs during startup still cause the FE to exit within a defined time limit and does not leave processes behind.
|
Restructured fault point to place an explicit check for environment variables up front and explicitly return early if none is set. |
| /* Timeout used for connecting to a server after the ready file has appeared */ | ||
| static long long connect_timeout_ms(void) | ||
| { | ||
| return env_timelimit_ms("COBO_CONNECT_TIMELIMIT", 60 * 1000); |
There was a problem hiding this comment.
Can we make this a define, one set through autoconf. I'm worried we may need to shift the time around for large systems, and I don't want to have to go back to the code.
I see we got the environment variable, but if someone's doing a system install that's not going to be set for users. Config system would be ideal, but we obviously aren't setup to read configs yet.
| cobo_connect_sleep = atoi(value); | ||
|
|
||
| /* seconds */ | ||
| if ((value = cobo_getenv("COBO_CONNECT_TIMELIMIT", ENV_OPTIONAL)) && atoi(value) > 0) |
There was a problem hiding this comment.
It's not obvious to me that we'd need to make any of these autoconf, ala the above comment about the timeouts.
But if you think making the defaults for these autoconf would be something an HPC center would need to do, then we should do so.
Spindle already had some code that was intended to time out after 60 seconds, but that did not actually work. These were:
COBO connect limit: COBO defines a default time limit of 60 seconds
Spindle/src/cobo/cobo.c
Lines 54 to 56 in 594befc
cobo_connect_hostnameto give up after that many seconds and log an error, then returning -1 if the socket was not successfully opened:Spindle/src/cobo/cobo.c
Line 494 in 594befc
Spindle/src/cobo/cobo.c
Lines 593 to 607 in 594befc
cobo_connect_hostnamewould return tocobo_open_treewhich would pass along the -1, butcobo_opendid not check the return value and proceeded into a barrier, which would then hang forever:Spindle/src/cobo/cobo.c
Lines 1530 to 1537 in 594befc
\nat the end of it:Spindle/src/cobo/cobo.c
Lines 1513 to 1516 in 594befc
Frontend startup timeout:
spindle_fe.ccdefines a startup timeout of 60 seconds:Spindle/src/fe/startup/spindle_fe.cc
Line 45 in 594befc
ldcs_audit_server_fe_md_waitfor_aliveas the time to wait for the alive message insendAndWaitForAlive():Spindle/src/fe/startup/spindle_fe.cc
Lines 511 to 516 in 594befc
spindleInitFE, did not check the return value ofsendAndWaitForAlive()and returned 0 unconditionally:Spindle/src/fe/startup/spindle_fe.cc
Lines 439 to 442 in 594befc
Client ready wait: The client repeatedly tries to stat the ready file and waits 0.1 seconds between stats. There is a limit of 600 retries (so a 60 second limit). However, nothing checks whether the retries exceeded the limit, so in the event that the 600 retries are used, it falls through to continuing client startup and proceeds to try to create the incoming FIFO:
Spindle/src/client/client_comlib/client_api_pipe.c
Lines 208 to 223 in 594befc
This PR makes changes to Spindle startup so that startup either succeeds or fails within a bounded time limit. This is done by 1) fixing the time limits that were already in the code but that had no effect; 2) ensuring that error conditions during startup return non-zero and that these failures propagate back to the original initialization call; and 3) by using non-blocking I/O for sockets and FIFOs so that calls to
accept()and FIFO opens can't hang indefinitely. The specific changes are:COBO_CONNECT_TIMELIMITis restored and applies to the startup process through the end of cachepath consensus. Failures in COBO tree setup are propagated to the caller instead of being ignored.spindleInitFEreturns -1 when the tree cannot be formed, the settings cannot be sent, or ALIVE does not arrive within the time limit.SPINDLE_READY_TIMELIMIT. The functionality of the ready file time limit is restored.spindle_bootstrapexits nonzero if its rank info or cache path query fails; it previously silently continued.spindle --start-sessionexits nonzero when the session daemon exits before it finishes starting.COBO_CONNECT_TIMELIMITthrough to the servers.cobo_open_treethe server's listen socket is now nonblocking and polls for its parent until the deadline; this prevents an indefinite hang inaccept()client_open_connection_pipethe client now opens its input FIFO nonblocking before creating the output FIFO and opening it nonblocking. This is retried onENXIOuntil the server has opened the read end or the time limit expires.To test this, a new test suite is added. Fault injection points are added to Spindle controlled by environment variables:
SPINDLE_TEST_BE_EXIT_HOSTS=host1,host2,...: BEs on these hosts exit as soon as they entercobo_open, before listening for their parent.SPINDLE_TEST_BE_FAULT_PHASE=tree|ready: for the below variables, iftree, exit or stall during tree formation (after the hostlist has been read from the parent); ifready, exit or stall just after the ready file has been created.SPINDLE_TEST_BE_EXIT=1,2,...: BEs with these COBO ranks will exit at the fault phase.SPINDLE_TEST_BE_STALL=1,2,...: BEs with these COBO ranks will stall at the fault phase.SPINDLE_TEST_BE_STALL_SECS=N: BEs that stall will stall for the given number of seconds.These modes are tested with the new startup failure test driver
run_startup_fault_tests.sh. Because the tests can take as long to run as all the existing tests combined, they are currently not enabled as part of the standard CI. Instead, the new tests run only upon request. It is not possible to run them on request until the changes have been merged into the default branch. I have run it manually on my own branch by temporarily changing the default branch so that we can see the results of the startup tests before merging. For those results, see https://github.com/ParaToolsInc/Spindle/actions/runs/41743592538Most of the tests actually complete relatively quickly, except for the SPANK plugin case, where the startup tests take 10m46sec in the above run. This is due to the SPANK plugin itself adding a 30 second delay while it tries to stop a frontend that never started. If we can get rid of that delay, then the slowest of the tests would be only 6 minutes.
I've also run the startup fault tests on Tuolumne and they pass there, as does the standard testsuite. If running the startup fault tests manually, note that they require a minimum of 3 nodes.
While failures are logged in the debug log, if enabled, there is not yet any attempt to determine which nodes failed and to present a list of them to the user. Error reporting will come in a future PR.