Created Internals (markdown)

Félix Saparelli 2023-05-07 11:22:58 +12:00
parent f121bae3d9
commit 6240914e33
1 changed files with 21 additions and 0 deletions

21
Internals.md Normal file

@ -0,0 +1,21 @@
The lifecycle of watchexec goes like this:
<img width="814" alt="paint" src="https://user-images.githubusercontent.com/155787/236479361-8729fd10-f4a4-4193-87ec-6cd877016d47.png">
So, events sourced from the file system, processes launched by watchexec, stdin, signals, and events manually sent in via `Watchexec::send_event()` all go into a channel. (That channel is actually also a priority queue, so that more urgent events like `Ctrl-C` can bypass a congested event queue, for example.)
That channel is consumed by the action worker. The action worker starts by the "throttle + filter" step, which is [a big loop](https://docs.rs/watchexec/latest/src/watchexec/action/worker.rs.html#40-120) that builds up a Vec of Events that pass [filters](https://docs.rs/watchexec/latest/watchexec/action/struct.WorkingData.html#structfield.filterer) within the "[throttle duration](https://docs.rs/watchexec/latest/watchexec/action/struct.WorkingData.html#structfield.throttle)". Once it's "out of throttle", ie it's reached the end of the throttle duration _and_ it has at least one event that's passed the filters, it makes an [`Action`](https://docs.rs/watchexec/latest/watchexec/action/struct.Action.html) which essentially wraps that `Vec<Event>`.
Then the [`action` handler](https://docs.rs/watchexec/latest/watchexec/action/struct.WorkingData.html#structfield.action_handler) is called, with this `Action` as argument. The handler can do anything (but should return quickly to avoid holding up Watchexec) but the main thing it does is it sets the [`Outcome`](https://docs.rs/watchexec/latest/watchexec/action/enum.Outcome.html) of the Action. That's an intentionally restrained way to give commands to Watchexec's internal process manager, which is called [`Supervisor`](https://docs.rs/watchexec/latest/watchexec/command/struct.Supervisor.html).
<img width="814" alt="paint" src="https://user-images.githubusercontent.com/155787/236486682-3ca318ac-e09e-41ce-90b2-f23c490e2ada.png">
What the supervisor does is it starts a process (technically it can supervise a _sequence_ of processes but let's keep things simple), and then holds a reference to it. When an action returns an Outcome, that may translate to a command to send to the Supervisor, like "send the process a signal" or "kill the process" or "wait for it to stop" or "start a new process" (which can only happen if the supervisor is idle aka doesn't have a running process at the moment). At the same time, the supervisor will also send an event down the events channel when the process quits, which are then handled in the exact same way by the whole machinery above.
<img width="814" alt="paint" src="https://user-images.githubusercontent.com/155787/236488708-0f35ec56-cae9-4016-bfed-ad36a364f3e7.png">
When the supervisor is about to start a process, it will prepare it then call the `pre_spawn` handler, which can be used to modify the process call a bit or to send a notification out or print a message or something. After that returns, the supervisor starts the process, and then it calls the `post_spawn` handler. That one's less useful but has the new process's PID for example.
All these things are exposed, and if some things aren't I'm likely to accept PRs to expose them in the public API (unless I strongly feel they shouldn't be).
(If you have graphics chops and can remake those awful mouse-drawn diagrams a little better, help appreciated!)