Plugins and extensions have variable runtimes (network calls, daemon queries, heavy computation), so each one declares its own runtime budget in its code. This page explains how that works.
The core runs plugins and extensions as subprocesses. Without any time
control, a hung plugin hangs xfetch (and the config load, for
extensions) forever.
All three crates (xfetch-plugin-api,
xfetch-extension-api, xfetch-effect-api) expose the
same helper:
pub fn with_timeout<T: Send + 'static>(
budget: Duration,
task: impl FnOnce() -> T + Send + 'static,
) -> Result<T, TimedOut>- The task runs on a worker thread.
-
Ok(task())is returned when it finishes withinbudget. -
Err(TimedOut)is returned when the budget elapses. The worker thread keeps running until the process exits — which is immediate, because the plugin/extension responds (or errors) and terminates.
-
Declare a
const BUDGETthat fits the work: ~2 s for local probes, 15–25 s for network calls. - Wrap everything (including reading the request from stdin) in the closure.
-
On
Err(TimedOut), respond with fallback lines (info plugins), fallback frames or the unmodified lines (effects), or exit with an error (animation plugins, extensions).
use std::time::Duration;
use xfetch_plugin_api::{read_info_plugin_args_or_default, with_timeout, write_info_lines};
const BUDGET: Duration = Duration::from_secs(10);
fn main() {
let lines = with_timeout(BUDGET, || {
let args = match read_info_plugin_args_or_default::<MyArgs>() {
Ok(v) => v,
Err(err) => {
eprintln!("{}", err);
std::process::exit(1);
}
};
do_work(&args)
})
.unwrap_or_else(|_| vec!["MyPlugin: timed out".to_string()]);
if let Err(err) = write_info_lines(lines) {
eprintln!("{}", err);
std::process::exit(1);
}
}
with_timeout is the standard for official plugins and
extensions (enforced in CI). As an extra safety net, the core can also kill
the process after an optional per-plugin deadline
(timeout_secs in the config), which protects against
third-party or uncooperative plugins. See the main xfetch documentation.