feat(plugins): renew the background budget while a task reports (#2055)

Closes #2053

A call that begins a background task gets the longer `background` budget, but it was armed once, so a task that kept calling `report` was still killed at the first deadline. Each `report` and `is-cancelled` now renews it (the deadline only moves forward; silent work still times out), up to a 30-minute ceiling per task. User Cancel, disable and unload still interrupt the task, teardown interrupts a running task promptly, and cancellation is counted once. WIT change is documentation only. Verified on macOS with a 75 s task.

Co-authored-by: chaoes <[email protected]>
Цей коміт міститься в:
WangTongchaoтаchaoes authored and GitHub committed 2026-11-08 04:46:58 +08:00
1 джерело 1446d5592c
коміт 6373bce476
66 files changed
+108 -38

No files matched your search

+28 -1
Переглянути файл
@@ -656,6 +656,33 @@ fn plugin_processes_end_with_their_owner() {
}
}
/// A task that keeps reporting progress is still working: each report renews
/// the background budget, so the call outlives the 400 ms it began with. A task
/// that goes quiet is still cut off (see `tasks_that_stop_reporting_are_cut_off`).
#[test]
fn a_reporting_task_outlives_its_background_budget() {
let f = fixture(Limits {
timeout: Duration::from_millis(1_000),
background: Duration::from_millis(2_000),
..Limits::default()
});
f.install(&misbehave_pkg());
if f.host.set_capability("misbehave", "process:git", true).is_err() {
return;
}
// Twenty-five waits of 100 ms: 2.5 s of wall time, more than the
// budget, spent inside the host rather than the plugin.
let started = std::time::Instant::now();
let out = f.run("misbehave", "patient", None).expect("a patient task").expect("a result");
let took = started.elapsed();
assert_eq!(out, "still here");
assert!(took >= Duration::from_millis(2_500), "it really ran the waits: {took:?}");
let info = f.host.info_of("misbehave").unwrap();
assert_eq!((info.status, info.stats.crashes, info.stats.errors), (PluginStatus::Active, 0, 0), "{info:?}");
}
/// A missing capability is an error the plugin can handle, not a trap.
#[test]
fn ungranted_capabilities_are_denied_not_trapped() {
@@ -932,7 +959,7 @@ fn idle_instances_are_reclaimed_and_schedules_reactivate() {
assert_eq!(f.host.instances(), 0);
let info = f.host.info_of("misbehave").unwrap();
assert_eq!((info.status, info.stats.reclaims), (PluginStatus::Inactive, 1));
assert_eq!(f.command_titles("misbehave").len(), 23, "contributions stay");
assert_eq!(f.command_titles("misbehave").len(), 24, "contributions stay");
assert_eq!(f.run("misbehave", "echo", Some("y")).unwrap().as_deref(), Some("y"));
assert_eq!(f.host.info_of("misbehave").unwrap().stats.activations, 2);
+18 -5
Переглянути файл
@@ -868,11 +868,7 @@ impl tasks::Host for StoreData {
let id = self.host.task_begin(self.client(), clip(title, crate::runtime::MAX_TITLE), cancellable, token);
self.open_tasks.push(id);
// The call may now run for the background budget.
let extended = self.call_started + self.limits.background;
if extended > self.deadline {
self.deadline = extended;
self.timeout = self.limits.background;
}
self.renew_background(self.call_started);
self.push(TaskRes { id })
}
}
@@ -887,6 +883,10 @@ impl tasks::HostTask for StoreData {
) -> wasmtime::Result<()> {
let id = self.table.get(&this)?.id;
let now = Instant::now();
// A task that is still reporting is still working: the call gets another
// background budget, so long work isn't cut off at the first deadline
// while a task that goes quiet still is.
self.renew_background(now);
// At most ~10 updates per second reach the UI.
if self.last_report.is_none_or(|t| now.duration_since(t).as_millis() >= 100) || total.is_some_and(|t| done >= t)
{
@@ -898,6 +898,8 @@ impl tasks::HostTask for StoreData {
fn is_cancelled(&mut self, this: Resource<TaskRes>) -> wasmtime::Result<bool> {
let id = self.table.get(&this)?.id;
// Asking whether to stop is the same sign of life as reporting is.
self.renew_background(Instant::now());
Ok(self.cancel.token().is_cancelled() || self.host.task_cancelled(id))
}
@@ -910,6 +912,17 @@ impl tasks::HostTask for StoreData {
}
impl StoreData {
/// Lets the running call reach `from` plus the background budget (the
/// longer of it and what was left), so a task that keeps saying it is alive
/// isn't cut off at the first deadline.
fn renew_background(&mut self, from: Instant) {
let extended = from + self.limits.background;
if extended > self.deadline {
self.deadline = extended;
self.timeout = self.limits.background;
}
}
/// What is left of the running call's wall-clock budget (host operations
/// can't be interrupted by the epoch callback, so they get this as timeout).
fn remaining(&self) -> Duration {
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
24726dac5ce264f5d451956def099e0002ec721e4828bc7852ad1dcabcb4bcba
54bad5e7b66e800a70e9e8a3189be3c18b815b1020f0718e9f63b6d0d116f700
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
833c378fabd0c3ec71e5c372e51049cb4fcd8d58e4992199eaf6090b3c1235d8
d3094365391dba5c9d65f506b4faa7abf3e4cf3906b94bbd058995efda105922
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
f757407f530839b8c43697e310926c5b141231bd8f2781ac92eee3a9de3f0f30
93247e39cda6a6174fa44dc27ea4b919efb563c5540b77937b740d2de94c7cbb
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
99cba883e8030d7679db366a4b18b8c456afccbcbc949e0d7411adc6411f2e00
1c5c1ce6d465aa201e39adf6c02e32f0d324d312b50371dd4f248b77ae8cda30
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
afcf583238dddd20d0d709ce7619fada0e60916f9e3c039569d37dec75354471
5f0877c4438f91dbb4069c733e9f2a214e366ec6e4c295c5c798a28ea75a5400
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
a1ce41144630be398bb63cffca26005955de0511a17c71dc286e7762b19e9cab
ff562c4c62faa7eaf112e12ab839e6bef4dcbdee42fa84d949e6857065432214
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
bbfc134f2121f301bbbcb0f0d596d7b3cc059455bba5ca78992e8bfa6630ce42
ea1066f976a18f765057a217b9de7a6c9871da321c80e0f7d4ccac6642dd44dd
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
b7f45420859ce9540e0aae1fd803eee13529ef63d0a2f0b6ba9e43a5958eb457
206f442d5360fc87b9e46ba5fdb7b7d0d4dc5ed550fd33181921c46463b5ea6f
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
041aa03873ff1d8133f181e86aab2ad3fda41e46503886754114f3abef3643da
1d647b76ef93c768c23b63b93d666d96323d19cb5011865e1acb5a8d178e0c3e
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
edcad61ab8e71cff2297edd9b902359d76ef04446bdfc42aae34420ced4d7f26
b777e29256630e58a6e92ef95c67db8d091d0fc07af24e8b91b087eaac4d269c
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
aad3a25c3522fbff6f48c0c1e9de23b6d0d5361ba6658b744ad1590e9fdbe255
b82d03052dd638871e278a691ad1ed09108ad4999678e6462780d686967eaf95
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
cfb432ea8943bf9b7e2f3ad3708df35260eebd04b3ac059c7643b378a35cd036
eb2efa2d8f0f1fd3579de8a965679755e99ed87b06faf528c20c6a558ca0e56c
+4
Переглянути файл
@@ -101,3 +101,7 @@ title = "childdrop"
[[contributes.commands]]
id = "childtrap"
title = "childtrap"
[[contributes.commands]]
id = "patient"
title = "patient"
+22
Переглянути файл
@@ -163,6 +163,28 @@ impl commands::Guest for Misbehave {
Ok(Some(process::run_command(&cmd, None, None)?.stdout))
}
}
// A task that keeps reporting is still working: the call may run
// past the background budget it began with.
"patient" => {
let task = openidea::plugin::tasks::begin("waiting", false);
let cmd = process::Command {
program: "git".into(),
args: vec!["hash-object".into(), "--stdin".into()],
env: Vec::new(),
};
let child = process::start(&cmd, None)?;
for step in 0..25 {
task.report(step, Some(25), Some("waiting"));
// Waiting blocks in the host for 100 ms: wall time passes
// while the plugin itself is not running.
let _ = child.wait(100);
}
// Let it finish on its own: no kill, no slow teardown.
child.close_stdin()?;
let _ = child.wait(5_000);
task.report(25, Some(25), Some("done"));
Ok(Some("still here".into()))
}
// `editor.write` is not granted: an error, not a trap.
"insert" => {
let editor = ctx.editor().ok_or_else(|| failed("no editor"))?;
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
82f53a3f36f965c76575a08609439e0bc9cea37f1aef7cc1671e9ec72a17f120
f72d5a14faaab7fc51dcefe37ba434615ffe47482772339e4aeffa0127f5ce79
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
a8d40f2224246e81e015d576a5fa70e83779af9fade614d61212b33fbb0961e9
6d7583de2f1ce90bec0078e9d3e5fe63db2f1c0fa1e564dbe3c65327c1c6b045
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
e60bcf1e179f6485e14de3755907c93344ef3cdf8158f01bea84343858ba7434
f9be5a80ddd63d623ec0e5a68e7f102c56955958874481cea52d4aabd0493dea
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
5cadc40061284bd5573f8323b02fb7ea7be18eb4323978b3f0b39337a9896774
39163b3ce79c043c21fb2f87e980db4d2a3f0ae8c6678e1865875301416ba791
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
5377825f5801850d6ed7ec989f6e954fae477d81cd3ce0d5a2b8baeb9febcf74
c060d8941dd7aba312c42ce5205bab91506be19450ac7b671381ce2088d44dfe
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
e1414e522a45066616475fae229c4dade82ba16eedef49aac8d4c014522a0684
b6b4ebd96fb1a879947ba2b12b2497eb466150e7c44f60257cff869b2f1db941
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
5a50246a300a15c8b9e50457094442fe35f0efe1534feb6f5cb407e052404c07
09863db73769c86bed14a4df0b0a4d480291aa1383172c406aba18a78e1d8989
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
61aaa8563701c6d4c147eaef87363ac0600c79fd9ef2ee8c8c1cc9c7e56794ed
61485dc08ad905dbb2d8d211ef8be8a6e88292d2d41ba11c15be54c672eccba0
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
ab0a7b4daf59fda5938b10c42628bb141c94bc0d82464254a6b0157d74e98477
1111db3957880758cf4ae2c34c95510846aef43c73ddeb03e901d5a10610544e
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
5813b46ce5ec12b9ca4bdfe61d49c63ed1af736238398a1c2e03ca7c3633469c
8d0c17fb643bb7368877ae7cfe342286bc1c8a1fc8fdc00c7491beb5c24390fe
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
208cf58ff413f2ef75abfd0a4280d6e38393acf4e2d0a30f9c4d3294aa9ec82f
ab15b403fb606ea226b491098f7b06335a03735d46a80a2d6e00cb042d201960
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
f83c2371ee5dd6c9d8d027ec3515fb7850141ac694d873ef0463a64ec6edcc9d
ab64135fcdeee8579e9b9613b47cccd94f66ff46d7c3a0507320a1364ffe8921
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
f5baecb3f80efee4244bc096039ae7ac926c8bc73eb08fdfe5ecf81a4e2c38c9
5b73dc19e676472cb94ec6c020d7fc6279b59aa8e8439b3840ece799035f1a2b
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
e4f62cf323d7186dcdc2972b0330086bc6279dcdc884d1997a84c1a589e739f8
c6763b53f7d4228678827234dfd02e413bb604c867b4ead8cd185316e6482897
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
216af779424458d3fe4de470f2358f21ebc15d62be3d285f59cba19d61e59f3d
1c735085dc8386adc94d4598eea8a2d5e582a98894f7742f83b97a9ddda7fb60
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
b2994bcb35737abbd001535308fcd4d3f0bab4cd61e580d8af1312451e760fe3
1d0dcf3288ec08122b64e51fceb0055f3627531b7152a3cccfe7d44ddd4887c2
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
6d5d5b09fd48e9b48891a91b3dd13f9d0d2f1771685a549c683295370233ae17
ae73ca009c7b36b3dd118a3a903c0132baf9c78baaceffe8d2a8a4ea8d182d1d
Бінарний файл не відображається.
+1 -1
Переглянути файл
@@ -1 +1 @@
5e6457d34c922284779cdcc42455ed1df9ed8b9245793134be566dcd3c8aab93
730d2bc7b15d2598840ec8668555f89cdf77b800c2ce21df7601880c57be6b2b
+3 -1
Переглянути файл
@@ -44,7 +44,9 @@ interface events {
}
/// Long-running work with a progress indicator. While a task is open, the
/// call that began it may run for the background budget (60 s) instead of 5 s.
/// call that began it may run for the background budget (60 s) instead of 5 s,
/// and each `report` or `is-cancelled` renews that budget: work that keeps
/// saying it is alive is not cut off, while work that goes quiet is.
interface tasks {
resource task {
/// `done` of `total` units (`none`: indeterminate), with a short status.
+3 -1
Переглянути файл
@@ -44,7 +44,9 @@ interface events {
}
/// Long-running work with a progress indicator. While a task is open, the
/// call that began it may run for the background budget (60 s) instead of 5 s.
/// call that began it may run for the background budget (60 s) instead of 5 s,
/// and each `report` or `is-cancelled` renews that budget: work that keeps
/// saying it is alive is not cut off, while work that goes quiet is.
interface tasks {
resource task {
/// `done` of `total` units (`none`: indeterminate), with a short status.