Skip to main content

cancel/
cancel.rs

1//! Cancels a link from another thread: a link with a deadline.
2//!
3//! ```sh
4//! cargo run --example cancel                       # built-in demo
5//! cargo run --example cancel -- 250 -o big <ld arguments>
6//! ```
7//!
8//! With arguments, the first is a time limit in milliseconds and the rest
9//! is a GNU ld command line; the link is cancelled if it takes longer. The
10//! link checks its [`CancelToken`] between pipeline stages and while it
11//! loads inputs and writes the output, then returns an error that
12//! [`CancelToken::is_cancellation`] recognizes. A cancelled link leaves any
13//! previous output file untouched.
14
15#[path = "support/objects.rs"]
16mod objects;
17
18use std::sync::mpsc;
19use std::time::{Duration, Instant};
20
21use qld::args::{CancelToken, InputAttrs, InputKind, LinkOptions, OutputBuffer, OutputKind};
22use qld::diag::Stderr;
23
24/// Runs `options` with a deadline: a watchdog thread cancels the link if
25/// it has not finished in time.
26fn link_with_deadline(mut options: LinkOptions, limit: Duration) -> qld::Result<()> {
27    let token = CancelToken::new();
28    options.cancel = Some(token.clone());
29    let (done, finished) = mpsc::channel::<()>();
30    let watchdog = std::thread::spawn(move || {
31        // Either the link finishes (the sender is dropped) or time runs out.
32        if finished.recv_timeout(limit) == Err(mpsc::RecvTimeoutError::Timeout) {
33            token.cancel();
34        }
35    });
36    let result = qld::link(&options, &Stderr::new(qld::PROGRAM_NAME));
37    drop(done);
38    let _ = watchdog.join();
39    result
40}
41
42fn main() -> qld::Result<()> {
43    let mut args = std::env::args().skip(1);
44    if let Some(limit) = args.next() {
45        let limit = Duration::from_millis(limit.parse().unwrap_or(1000));
46        let argv: Vec<String> = std::iter::once("ld".to_string()).chain(args).collect();
47        let qld::ParseOutcome::Link(options) = qld::parse_gnu(&argv)? else {
48            return Ok(());
49        };
50        let start = Instant::now();
51        let result = link_with_deadline(*options, limit);
52        match &result {
53            Ok(()) => eprintln!("linked in {:?}", start.elapsed()),
54            Err(error) if CancelToken::is_cancellation(error) => {
55                eprintln!("{error} after {:?}", start.elapsed());
56            }
57            Err(_) => {}
58        }
59        return result;
60    }
61
62    // Without arguments: link the in-memory demo program twice, once with
63    // plenty of time and once cancelled before it starts.
64    let mut options = LinkOptions::new();
65    options.kind = OutputKind::StaticExecutable;
66    options.push_input(
67        InputKind::bytes("main.o", objects::main_object()),
68        InputAttrs::default(),
69    );
70    options.push_input(
71        InputKind::bytes("answer.o", objects::answer_object(42)),
72        InputAttrs::default(),
73    );
74    let buffer = OutputBuffer::new();
75    options.output_buffer = Some(buffer.clone());
76
77    link_with_deadline(options.clone(), Duration::from_secs(10))?;
78    println!(
79        "with a 10 s deadline: linked {} bytes",
80        buffer.take().map_or(0, |b| b.len())
81    );
82
83    let token = CancelToken::new();
84    token.cancel();
85    options.cancel = Some(token);
86    match qld::link(&options, &Stderr::new(qld::PROGRAM_NAME)) {
87        Err(error) if CancelToken::is_cancellation(&error) => {
88            println!(
89                "cancelled before it started: {error}; output empty: {}",
90                !buffer.is_filled()
91            );
92        }
93        other => println!("unexpected: {other:?}"),
94    }
95    Ok(())
96}