Repository navigation
feat(remote): run a worker behind a remote wrapper that records its instance and outcome - #75
Merged
Merged
Conversation
…nstance and outcome Signed-off-by: Feathbow <feathbow@gmail.com>
…ting one Signed-off-by: Feathbow <feathbow@gmail.com>
…n open after a refusal Signed-off-by: Feathbow <feathbow@gmail.com>
…d read a removed request as missing Signed-off-by: Feathbow <feathbow@gmail.com>
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.
Why
A worker on another host runs behind an SSH client, so the local child the core observes is the client, and its exit and end of file describe the connection. The remote side needs its own process owner that records which process it started, how it ended and what it wrote, in a form the core can read separately from the worker's protocol stream. Refs #17.
What
invar remote runcreates an instance directory, takes a lock in it for its whole life, and writeslaunching.json. It starts its child,invar remote exec, in a new process group led by that child, with exactly the declared environment and working directory. It publishesinstance.json(host, nonce, wrapper and worker process ids, process group) before it releases a barrier, so the worker runs only after its instance is on disk. A close-on-exec status pipe separates a failed start, recorded infailure.json, from any exit of the worker.terminated.jsonandkilled.jsonrecord the signals that were delivered. After an admitted request, the outcome is therefore written no sooner than the grace after it. When the child has ended and its stdout is drained,outcome.jsonrecords the instance, the exit or signal, the time and the stdout count and digest.refused.<n>.jsonwith its own instance, the request and the reason, then removes it, so a request naming this instance can still terminate it.invar remote terminatewrites the request when a live wrapper holds the instance and holds no request, waits for the answer to this request and reports it as confirmed, refused, unanswered or unrequested. A repeated call writes nothing and reports the confirmation of its own request; while another request is admitted, a call writes nothing and reports its own request as unrequested, never the answer to the other. Without a live wrapper it writes nothing and signals nothing.invar remote statusreads the files in the reverse order of writing, counts a file removed before it is read as absent, and reports whether a live wrapper holds the lock.Invar.Remote.Controlholds the record types, their encoders and one decoder. It refuses a file with another format or a missing or extra field, a file that names another launch or instance, an outcome without its instance, a launch failure together with an outcome, a kill without its termination, a confirmation without its accepted request, a refusal that names another instance, and a refusal of a request that would be accepted.Control.admitRequestis the one place that decodes a request and matches it against the instance.Liveness is a lock held by the wrapper, released by the kernel when the wrapper ends. This replaces comparing a process id with a start time and boot identity: a reused process id cannot make a dead wrapper look alive, and the same check works on Linux and macOS. The nonce chosen per launch already makes the instance identity unique, so the identity is the host, the nonce and the two process ids.
The child is placed in its own process group as it is created, before the wrapper publishes the instance. If the child created its own group after it started, a termination request could arrive before that group existed.
Group signals reach only processes that stay in the worker's group. A process that leaves the group while holding the worker's stdout is out of their reach, and the wrapper waits for it to close stdout. When only the exited leader is left in the group as the grace ends, Linux accepts the KILL and macOS refuses it, so
killed.jsonis written on Linux only in that case.Validation
The core suite, formatting, lint, the type fixtures and
cabal checkpass. The test suite runs the built executable with real local child processes. It covers the start barrier and the status before, during and after a run, a program that cannot start, unchanged forwarding with a final line that has no newline, a reader that goes away while the worker writes a mebibyte, termination of a worker that has its own child, a kill after the grace, a group member that ignores TERM and holds stdout after the leader exits, a hangup to the wrapper, a request with no live wrapper, a refused request followed by a matching one that terminates the worker, and a repeated request and a different one, neither of which replaces the admitted request or takes its confirmation. The decoder's refusals are tested in one place. The core suite passed in repeated consecutive runs.