# Process management This page explains how `plesty-server` starts, watches and stops other programs — device servers that should stay up for days, and jobs (an install, a field test) that run for minutes. It assumes no prior background; the terms are introduced as they come up. ## The problem Several `plesty-server` processes look at the same bench at the same time: a CLI command you type, the GUI, the headless `fleet run` service, the remote agent. Each of them must be able to answer *is the pm100d server running?*, *what did the install job print?*, *why did the server die?* — about processes **another** `plesty-server` process started, possibly yesterday. Two facts about operating systems make that harder than it sounds. **1. A child belongs to its parent.** When a program starts another one (its *child*), the parent is told when the child exits and must *collect* that exit (`wait`). Until it does, the child lingers as a **zombie**: it is dead, but the process table still lists it, and a liveness check such as `kill(pid, 0)` says "exists". If the CLI that started a server exits, the server's exit code is lost for everyone else; if the GUI started it and the GUI is busy, the dead server still looks alive to the CLI. The parent/child relationship is exactly what we do *not* want between `plesty-server` and the processes it manages. **2. Signals are the only way to ask nicely.** To stop a program from the outside you send it a *signal*: `SIGINT` is "you were interrupted" (what Ctrl-C sends), `SIGTERM` is "please terminate", `SIGKILL` ends it without asking. A plesty-lib device server handles the interrupt by closing the instrument session cleanly (`[SERVER] Shutting down...`); a kill leaves the instrument in whatever state it was in. So the order is always *ask, wait, insist, then force*. ## The design: nothing is a child Every server and every job is started through the **launcher** (`plesty.server.services.launch`), a tiny program run as `python -m plesty.server.services.launch` — `plesty-server _launch` inside a bundle, see `reexec` — with a JSON spec on stdin. It 1. **detaches** itself so that nobody has to reap it (`host.detach` — on POSIX a fork + new session, after which the intermediate parent exits and the waiter is adopted by `init`; on Windows the launcher process simply *is* the detached process), 2. prints the pid of the detached **waiter** — that is the pid everybody tracks, 3. starts the real command with stdout and stderr appended to the **log file**, forwards `SIGINT`/`SIGTERM` to it, waits, 4. writes the exit code to the **exit file** (`.exit`, written atomically via a temp file) and appends `=== exit N at