1 stable release

1.0.0 May 12, 2022

#517 in Unix APIs

MIT license

37KB
455 lines

Maintenance License Coverage Rust

calloop-subprocess

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.

Example

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();

event_loop
    .handle()
    .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.
                stopper.stop();

                // 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.
        false
    })
    .unwrap();

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

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

Dependencies

~7–17MB
~228K SLoC