pub fn ctrlc_frame() -> CtrlcBuilderprint only.Expand description
§Handling Ctrl-C
The ctrlc crate provides a low-level cross-platform
way to set up a handler for Ctrl-C. cu builds wrappers around it so it can
be intergrated with other internals, such as prompts.
§Action Frames
Most of the time, custom Ctrl-C behavior is for executing some long running
tasks and give the user the ability to abort it.
The execute function pushes a new frame
to an internal stack, then executes the task synchronously on the same thread.
**The task itself is responsible for periodically checking the received
signal object if it has been aborted (by calling ctrlc.check?).
The frame will only be removed from the stack when the task returns.
Note that different threads can spawn Ctrl-C frames, and those frame
may not finish in order. When user hits Ctrl-C, only the most recently
added frame (the top-most of the stack) will be notified.
It will continue to notify the frame until the task ends and removes
the frame from the stask.
§Async Behavior
The async version of the API, co_execute,
is exactly the same, other than it takes an async closure instead.
Even though in the async case, we can let the runtime drop the future
to abort it without explicit checks, the explicit checks make it clearer
and easier to reason about program states when aborting.
If you prefer automatically cancellation, consider this pattern:
let (send, mut recv) = tokio::sync::mpsc::unbounded_channel();
let result = cu::cli::ctrlc_frame()
.on_signal(move |_| { let _ = send.send(()); })
.co_execute(async move |ctrlc| {
let waiter = async move {
loop {
if recv.recv().await.is_none() {
return;
}
if ctrlc.should_abort() {
return;
}
}
};
tokio::select! {
result = my_long_running_task() => {
return result;
}
_ = waiter => {
// does not matter what the value is -
// co_execute will ensure None is returned
// when aborted
return Ok(0);
}
}
}).await?;
match result {
Some(x) => cu::info!("valud is: {x}"),
None => cu::error!("aborted!"),
}
async fn my_long_running_task() -> cu::Result<i32> {
// your task here can get aborted without checking the signal
// which can be dangerous and have unintended effects!
Ok(42)
}§Return Value
The return value of the inner cu::Result<T> will be transformed into
cu::Result<Option<T>>, where:
Ok(Some(value)): the action was not aborted, and produced a value.Ok(None): the action was aborted.- The definition of “aborted” can be customized by setting the
abort_threshold()on the builder
- The definition of “aborted” can be customized by setting the
Err(e): either the inner task returned error, or some other error happened in the framework (for example, when joining the task thread/future)
use std::thread;
use std::time::Duration;
match cu::cli::ctrlc_frame().execute(|ctrlc| {
for _ in 0..10 {
cu::print!("please press Ctrl-C");
thread::sleep(Duration::from_secs(2));
ctrlc.check()?; // returns Err if signaled
}
cu::Ok(42)
}) {
Ok(None) => cu::info!("was aborted!"),
Ok(Some(n)) => cu::info!("was finished: {n}"),
Err(e) => cu::error!("error: {e:?}"),
}§Fallback
If the Ctrl-C framework failed to initialize, this may fallback to simply running
the task without the ability to receive signals.
§Global Handlers
Action frames should be used whenever possible.
If you do need a global handler for Ctrl-C, use
cu::cli::add_global_ctrlc_handler. This is because the ctrlc crate
only allows one global handler.
Global handlers are called after the action frame is notified (if any),
and called in reverse order of registration.
The underlying handler is lazily set up whenever a global handler
or action frame is added. If there are no longer any action frames
and there are no global handlers, the underlying handler
will call std::process::exit(1) to terminate.