New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Breaking changes for watchexec lib #601
Conversation
hey @passcod, this looks awesome! there are a couple of questions that i was curious about! some of which i believe you posed in the past as well, if memory is not mistaken me!
the use of the |
postspawn is run immediately after spawning, so that's not how, but the supervisor issues a ProcessCompletion event when a process ends, which is how you get notified (in the action handler) |
no. that's an application concern exterior to watchexec as a library |
aha, okie! as in the name yea.. that would make sense haha! would it be this let event = Event {
tags: vec![
Tag::Source(Source::Internal),
Tag::ProcessCompletion(status.map(Into::into)),
],
metadata: Default::default(),
}; also, would there be a way to identify which |
oh okie ! :) |
not yet, but I'll add supervisor id to it I think |
i like that idea, yess! :D |
(I haven't forgotten about this, I've just been moving house and dayjob got intense at the same time.) |
ohh, @passcod hope you're doing and managing well with all of this going on! certainly take all the time you need! can't have our number one maintainer burn out ! :)) |
Made a bunch of progress and also expanded the scope quite a bit; going for a big simplification of the interface while still keeping all (or more!) of the features. The three big changes so far are:
And I'm working on making reconfiguration more natural, like just being able to change the config directly, not keep a clone of I moved around the multi-process stuff a bit, so that now it's:
That means that it's no longer needed to pass EventSet to anything but apply(), which simplifies a bunch of types too. |
heyy @passcod, these changes look awesome, wow!
ohh, wow, this change is amazing! you are so right, it does seem to make things a lot easier!
oooo, these will be very interesting to dive into and see how to take advantage of these when using one quick question, if that is alright? before, when calling |
your understanding is correct! note that the latter is not implemented yet, but that's the plan. essentially it will build up an Outcome::both though, nothing more complicated than that. the real interesting thing is with quit() or remove(): those will wait until all outcomes or the supervisor's respectively are done before running, so a graceful quit can be done like
oh and also the order will be kept only per supervisor, all supervisors will be affected in parallel so in the pseudocode above, all processes would get gracefully stopped and then removed at the same time, not one after the other, and once they're all done watchexec would quit |
you maybe are underselling how clever of you this is ! the simplicity of implementing it this way shouldn't be understated, i think.. :) super nice @passcod ! :))
ooo, wow! if the previous is it a correct interpretation, that remove() acts on one |
b887c70
to
4181c99
Compare
Change theCommand
type to include a newIsolation
enum, for now onlyProcessGroup
, per command. (Later to havePty
and perhaps evenCgroup
)reconfigure()
methodAction::quit()
workraw_args
on Windowson_action
handler should be cheap because it blocks the core event loop.Add supervisorid to processcompletion?This is about providing a tie-back to which process it was that completed, relative to the supervisor that spawned it, now that there can be multiple in flight.Tidy the argument situation for the spawn method(s)spawn*
methods in the API has a lot of arguments, and clippy is complaining. It could be that the solution is to do nothing, as anything else than arguments is even more unwieldy, but a little investigations into the options available is warranted.Always grab all I/O streams and control how they're hooked up between commands and the main process(scoped out)Draft release/upgrading notes
Final changelog here: https://github.com/watchexec/watchexec/blob/main/crates/lib/CHANGELOG.md
General
Watchexec
the core experience rather than providing the kitchensink / components so you could build your own from the pieces; that helps the cohesion of the whole and simplifies many patterns.watchexec_events
andwatchexec_signals
crates) are removed.watchexec-supervisor
.pid1
crate.Watchexec
Watchexec::new()
now takes theon_action
handler. As this is the most important handler to define and Watchexec will not be functional without one, that enforces providing it first.Watchexec::with_config()
lets one provide a config upfront, otherwise the default values are used.Watchexec::default()
is mostly used to avoid boilerplate in doc comment examples, and panics on initialisation errors.Watchexec::reconfigure()
is removed. Use the publicconfig
field instead to access the "live"Arc<Config>
(see below).Config
InitConfig
andRuntimeConfig
have been unified into a singleConfig
struct.WorkingData
structures, all of the config is now flat in the sameConfig
. That makes it easier to work with as all that's needed is to pass anArc<Config>
around, but it does mean the event sources are no longer independent.tokio::sync::watch
for some values, andHandlerLock
for handlers, and so on, everything is now a newChangeable
type, specialised toChangeableFn
for closures andChangeableFilterer
for the Filterer.signal_change()
method which must be called after changes to the config; this is taken care of when using the methods onConfig
. This is required for the few places in Watchexec which need active reconfiguration rather than reading config values just-in-time.Watchexec::reconfigure()
and keeping a clone of the config around, anArc<Config>
is now "live" and changes applied to it will affect the Watchexec instance directly.command
/commands
are removed from config. Instead use the Action handler API for creating new supervised commands.command_grouped
is removed from config. That's now an option set onCommand
.action_throttle
is renamed tothrottle
and now defaults to50ms
, which is the default in Watchexec CLI.keyboard_emit_eof
is renamed tokeyboard_events
.pre_spawn_handler
is removed. UseJob#set_spawn_hook
instead.post_spawn_handler
is removed. UseJob#run
instead.Command
The structure has been reworked to be simpler and more extensible. Instead of a Command enum, there's now a Command struct, which holds a single
Program
and behaviour-altering options.Shell
has also been redone, with less special-casing.If you had:
You should now write:
Program::Shell
fieldargs: Vec<String>
lets you pass (trailing) arguments to the shell invocation:args
field ofCommand::Shell
is now theoptions
field ofShell
.Shell
has a new fieldprogram_option: Option<Cow<OsStr>>
which is the syntax of the option used to provide the command. Ie for most shells it's-c
and forCMD.EXE
it's/C
; this makes it fully customisable (including its absence!) if you want to use weird shells or non-shell programs as shells.Shell::Powershell
is removed.raw_arg
instead ofarg
to avoid quoting issues.Command
can no longer take a list of programs. That was always quite a hack; now that multiple supervised commands are possible, that's how multiple programs should be handled.command_grouped
option is now Command-level, so you can start both grouped and non-grouped programs.reset_sigmask
option to control whether commands should have their signal masks reset on Unix. By default the signal mask is inherited.Errors
RuntimeError::NoCommands
,RuntimeError::Handler
,RuntimeError::HandlerLockHeld
, andCriticalError::MissingHandler
are removed as the relevant types/structures don't exist anymore.RuntimeError::CommandShellEmptyCommand
andRuntimeError::CommandShellEmptyShell
are removed; you can constructShell
with empty shell program andProgram::Shell
with an empty command, these will at best do nothing but they won't error early through Watchexec.on_error
handler is now sync only and no longer returns aResult
; as such there's no longer the weird logic of "if theon_error
handler errors, it will call itself on the error once, then crash".on_error
, you should instead use non-async calls (liketry_send()
for Tokio channels). The error handler is expected to return as fast as possible, and not do blocking work if it can at all avoid it; this was always the case but is now documented more explicitly.Action
The process supervision system is entirely reworked. Instead of "applying
Outcome
s", there's now aJob
type which is a single supervised command, provided by the separatewatchexec-supervisor
crate. The Action handler itself can only create new jobs and list existing ones, and interaction with commands is done through theJob
type.The controls available on
Job
are now modeled on "real" supervisors like systemd, and are both more and less powerful than the oldOutcome
system. This can be seen clearly in how a "restart" is specified. Previously, this was anOutcome
combinator:Now, it's a discrete method:
Previously, a graceful stop was a mess:
Now, it's again a discrete method:
The
stop()
andstart()
methods also do nothing if the process is already stopped or started, respectively, so you don't need to check the status of the job before calling them. Thetry_restart()
method is available to do a restart only if the job is running, with thetry_restart_with_signal()
variant for graceful restarts.Further, all of these methods are non-blocking sync (and take
&self
), but they return aTicket
, a future which resolves when the control has been processed. That can be dropped if you don't care about it without affecting the job, or used to perform more advanced flow control. The specialto_wait()
method returns a detached, cloneable, "wait()" future, which will resolve when the process exits, without needing to hold on to theJob
or a reference at all.Here's a simplified example which starts a job, waits for it to end, then (re)starts another job if it exited successfully: