Skip to main content

ctrlc_frame

Function ctrlc_frame 

Source
pub fn ctrlc_frame() -> CtrlcBuilder
Available on crate feature print 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
  • 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.