pub fn with_config(config: Config) -> Result<Self, CriticalError> {
		debug!(?config, pid=%std::process::id(), version=%env!("CARGO_PKG_VERSION"), "initialising");
		let config = Arc::new(config);
		let outer_config = config.clone();

		let notify = Arc::new(Notify::new());
		let start_lock = notify.clone();

		let (ev_s, ev_r) =
			priority::bounded(config.event_channel_size.try_into().unwrap_or(u64::MAX));
		let event_input = ev_s.clone();

		trace!("creating main task");
		let handle = spawn(async move {
			trace!("waiting for start lock");
			notify.notified().await;
			debug!("starting main task");

			let (er_s, er_r) = mpsc::channel(config.error_channel_size);

			let mut tasks = JoinSet::new();

			tasks.spawn(action::worker(config.clone(), er_s.clone(), ev_r).map_ok(|()| "action"));
			tasks.spawn(fs::worker(config.clone(), er_s.clone(), ev_s.clone()).map_ok(|()| "fs"));
			tasks.spawn(
				signal::worker(config.clone(), er_s.clone(), ev_s.clone()).map_ok(|()| "signal"),
			);
			tasks.spawn(
				keyboard::worker(config.clone(), er_s.clone(), ev_s.clone())
					.map_ok(|()| "keyboard"),
			);
			tasks.spawn(error_hook(er_r, config.error_handler.clone()).map_ok(|()| "error"));

			while let Some(Ok(res)) = tasks.join_next().await {
				match res {
					Ok("action") => {
						debug!("action worker exited, ending watchexec");
						break;
					}
					Ok(task) => {
						debug!(task, "worker exited");
					}
					Err(CriticalError::Exit) => {
						trace!("got graceful exit request via critical error, erasing the error");
						// Close event channel to signal worker task to stop
						ev_s.close();
					}
					Err(e) => {
						return Err(e);
					}
				}
			}

			debug!("main task graceful exit");
			tasks.shutdown().await;
			Ok(())
		});

		trace!("done with setup");
		Ok(Self {
			config: outer_config,
			start_lock,
			event_input,
			handle: Arc::new(AtomicTake::new(handle)),
		})
	}
Instantiates a new `Watchexec` runtime with a config.

This is generally not needed: the config can be changed after instantiation (before and
after _starting_ Watchexec with `main()`). The only time this should be used is to set the
"unchangeable" configuration items for internal details like buffer sizes for queues, or to
obtain Self unwrapped by an Arc like `new()` does.
crates/lib/src/watchexec.rs