#events #loop #unix #subprocess #multiprocessing

bin+lib calloop-subproc

Subprocess support for the Calloop event loop

1 stable release

Uses new Rust 2021

1.0.0 May 12, 2022

#680 in Rust patterns

MIT license

455 lines

Maintenance License Coverage Rust


Subprocess handling event source for the Calloop event loop.

Calloop is built around the concept of event sources eg. timers, channels, file descriptors themselves, which generate events and call a callback for them. This crate provides two kinds of event sources that let you manage subprocess:

  • a chain, which runs a series of commands and generates a success/failure event
  • a listener, which runs a single command and generates an event based on lines of output

Error handling

Errors in this crate are classified into two high-level kinds, as is the custom for Calloop.

[LaunchError] might be generated by an event source's process_events() method when a critical error is encountered. This generally means that the event source itself can't continue sensibly and should be removed from the event loop.

The [ErrorEvent] type is concerned with errors of the subprocess it's managing, including the command not being found or requiring permissions that the process doesn't have. These will be contained in the type of event that is given to the callback.

The main way that these are different is: if there's a problem using whatever underlying mechanism we use to spawn a subprocess or get results back from it, then a [LaunchError] is produced then and there. But if the underlying mechanics for setting up a subprocess succeed, and it's just that the command contains a typo and can't be found, then it's perfectly possible to generate an event for that. It will just happen to contain the IO error you'd expect when trying to run the command directly.

Releasing file descriptors

To avoid "holding on" to file descriptors (and just the memory associated with the event source itself), you must honour the [calloop::PostAction] returned from process_events() and remove the event source if requested.

This is automatically done for top level event sources ie. those added by insert_source(). If you use it as part of a composed event source, you must either manage reregistration yourself, or wrap the source with TransientSource.


This runs ls -a and prints the events received.

use calloop::{EventLoop, LoopSignal};
use calloop_subproc::{Command, SubprocListen};

let ls_cmd = Command::new("ls").with_args(["-a"]);
let listener = SubprocListen::new(ls_cmd).unwrap();

let mut event_loop: EventLoop<LoopSignal> = EventLoop::try_new().unwrap();

    .insert_source(listener, |event, _, stopper| {
        // What kind of event did we get this time?
        let msg = match event {
            // The subprocess was just started.
            calloop_subproc::ListenEvent::Start => "Subprocess started".to_owned(),
            // We got a line of output from the subprocess.
            calloop_subproc::ListenEvent::Line(line) => format!("Output: {}", line),
            // The subprocess ended.
            calloop_subproc::ListenEvent::End(res) => {
                // Since the subprocess ended, we want to stop the loop.

                // Show why the subprocess ended.
                match res {
                    Ok(()) => "Subprocess completed".to_owned(),
                    Err(error) => format!("Subprocess error: {:?}", error),

        // Print our formatted event.
        println!("{}", msg);

        // This callback must return true if the subprocess should be
        // killed. We want it to run to completion, so we return false.

    .run(None, &mut event_loop.get_signal(), |_| {})

This document is kept up-to-date with cargo-rdme.


~149K SLoC