Merge pull request #6779 from dotCooCoo/fix/script-sleep-reads-doc-cancel

Scripts: a canceled request stops a run asleep in $.sleep at its next slice
This commit is contained in:
BFlat
2026-11-12 13:18:11 +07:00
committed by GitHub
6 changed files with 81 additions and 4 deletions
+4 -3
View File
@@ -1787,6 +1787,10 @@ impl Session {
if let Some(e) = job.host.dialogs.take_failure() {
return Some(self.script_failed(job, &e));
}
// A canceled request ends a sleep or a wait at this pump.
if vectorcraft_doc::cancel::check().is_err() {
self.script_stop.set();
}
if (job.host.waiting.get() || job.host.asleep().is_some() || job.host.dialogs.is_waiting()) && !self.script_stop.is_set() {
JOBS.with(|jobs| jobs.borrow_mut().insert(key.id, job));
self.script_key = Some(key);
@@ -1798,9 +1802,6 @@ impl Session {
let e = RunError { kind: RunErrorKind::Limit(kind), message: format!("limit reached: {kind:?}"), line: 0, column: 0 };
return Some(self.script_failed(job, &e));
}
if vectorcraft_doc::cancel::check().is_err() {
self.script_stop.set();
}
job.host.calls_this_slice.set(0);
let outcome = run_slice(self, &mut job);
match outcome {
+20
View File
@@ -217,6 +217,26 @@ fn dialog_loop_stops_or_exhausts() {
assert_eq!(art(&s), before);
}
#[test]
fn a_run_waiting_at_a_dialog_stops_at_the_next_slice_once_its_work_is_canceled() {
let mut s = one_doc();
let before = art(&s);
let token = crate::cancel::CancelToken::new();
s.script_frames = true;
let source = "vc.run('shape.rectangle', {x: 20, y: 0, width: 5, height: 5}); alert('x'); vc.log('answered');";
assert_eq!(s.execute("script.run", &json!({"source": source, "name": "d.js"})).unwrap()["running"], true);
assert!(crate::cancel::scope(&token, None, || s.script_pump()).is_none());
assert!(s.script_dialog().is_some());
token.cancel();
let ended = crate::cancel::scope(&token, None, || s.script_pump());
assert!(ended.expect("the run goes on waiting for an answer").is_err());
let report = s.script_report().unwrap();
assert_eq!((report["kind"].as_str(), &report["log"]), (Some("stopped"), &json!([])), "{report}");
assert_eq!(report["dialogs"][0]["answer"], "stop");
assert_eq!(s.script_dialog(), None);
assert_eq!(art(&s), before);
}
#[test]
fn dialog_text_limits_and_controls() {
let mut s = one_doc();
+31
View File
@@ -5,6 +5,7 @@ use std::time::Duration;
use serde_json::{Value, json};
use crate::cancel::{self, CancelToken};
use crate::script::{TEST_ELAPSED, TEST_NO_CLOCK};
use crate::*;
@@ -204,6 +205,36 @@ fn a_sleeping_run_stops_when_asked() {
TEST_ELAPSED.set(None);
}
#[test]
fn a_sleeping_run_stops_at_the_next_slice_once_its_work_is_canceled() {
for (asleep_until, when) in
[(None, "before its first slice"), (Some(Duration::ZERO), "while it sleeps"), (Some(Duration::from_secs(1)), "at its wake time")]
{
let mut s = Session::new();
s.execute("file.new", &json!({})).unwrap();
let before = (format!("{:?}", s.documents()[0].doc.layers), s.documents()[0].history.undo.len());
s.script_frames = true;
TEST_ELAPSED.set(Some(Duration::ZERO));
let source = "vc.run('shape.rectangle', {x: 0, y: 0, width: 5, height: 5}); $.sleep(1000); vc.log('woke'); \
vc.run('shape.rectangle', {x: 9, y: 0, width: 5, height: 5});";
s.execute("script.run", &json!({"source": source, "name": "t.js"})).unwrap();
let token = CancelToken::new();
if let Some(at) = asleep_until {
assert!(cancel::scope(&token, None, || s.script_pump()).is_none(), "{when}");
assert_eq!(s.script_sleep_left(), Some(Duration::from_secs(1)), "{when}");
TEST_ELAPSED.set(Some(at));
}
token.cancel();
let ended = cancel::scope(&token, None, || s.script_pump());
TEST_ELAPSED.set(None);
assert!(ended.unwrap_or_else(|| panic!("{when}: the run goes on waiting")).is_err(), "{when}");
let report = s.script_report().unwrap();
assert_eq!((report["kind"].as_str(), &report["log"]), (Some("stopped"), &json!([])), "{when}: {report}");
assert!(!s.script_running(), "{when}");
assert_eq!((format!("{:?}", s.documents()[0].doc.layers), s.documents()[0].history.undo.len()), before, "{when}");
}
}
#[test]
fn sleep_takes_milliseconds_from_0_to_1000() {
let mut s = Session::new();
+24
View File
@@ -845,6 +845,30 @@ fn a_person_can_let_a_growing_run_go_on_or_stop_it() {
assert_eq!(state(&s), before);
}
#[test]
fn a_run_waiting_to_grow_stops_at_the_next_slice_once_its_work_is_canceled() {
TEST_GROWTH.with(|g| g.set(Some(20)));
let mut s = two_docs();
let before = state(&s);
s.script_frames = true;
run(&mut s, "for (var i = 0; i < 25; i++) vc.run('shape.rectangle', {x:i,y:1,width:1,height:1});").unwrap();
let token = crate::cancel::CancelToken::new();
for _ in 0..1_000 {
if s.script_waiting() {
break;
}
assert!(crate::cancel::scope(&token, None, || s.script_pump()).is_none());
}
assert!(s.script_waiting());
token.cancel();
let ended = crate::cancel::scope(&token, None, || s.script_pump());
TEST_GROWTH.with(|g| g.set(None));
assert!(ended.expect("the run goes on waiting for Continue").is_err());
assert_eq!(s.script_report().unwrap()["kind"], "stopped");
assert!(!s.script_running());
assert_eq!(state(&s), before);
}
#[test]
fn a_file_saved_before_a_failure_stays_written_and_is_named() {
let path = std::env::temp_dir().join(format!("vc-script-save-{}.svg", std::process::id()));