diff --git a/changelog.d/14466-snap-census-queues-at-cut.md b/changelog.d/14466-snap-census-queues-at-cut.md new file mode 100644 index 0000000000..e936d63c02 --- /dev/null +++ b/changelog.d/14466-snap-census-queues-at-cut.md @@ -0,0 +1 @@ +Name the nextTick queue, scheduled promise resolves and timers/immediates pending at the startup snapshot cut in the instrumented census, each with its owner init and callback. diff --git a/crates/perry-runtime/src/builtins/globals.rs b/crates/perry-runtime/src/builtins/globals.rs index 7cccdf36c3..2a71cd2cdd 100644 --- a/crates/perry-runtime/src/builtins/globals.rs +++ b/crates/perry-runtime/src/builtins/globals.rs @@ -821,6 +821,45 @@ pub(crate) fn test_seed_queued_job_owner(async_id: u64, resource: f64) { }); } +/// SNAPK4 census: the queued nextTick jobs in queue order, one +/// `(owner, code)` per job: the init that queued it +/// (`AsyncContextSnapshot::snap_init`, stamped at enqueue) and its callback's +/// code address. None while the queue is borrowed. +#[cfg(perry_gc_instruments)] +pub(crate) fn snap_pending_ticks() -> Option> { + QUEUED_MICROTASKS.with(|q| { + q.try_borrow().ok().map(|q| { + q.iter() + .map(|job| { + ( + job.context.snap_init, + crate::promise::snap_closure_code( + job.callback as *const crate::closure::ClosureHeader, + ), + ) + }) + .collect() + }) + }) +} + +/// SNAPK4 census tests: queue a nextTick job owned by `owner` directly. +#[cfg(all(test, perry_gc_instruments))] +pub(crate) fn test_push_tick(callback: i64, owner: u32) { + let mut context = crate::async_context::capture_context(); + context.snap_init = owner; + QUEUED_MICROTASKS.with(|q| { + q.borrow_mut().push_back(QueuedMicrotask { + callback, + context, + async_id: 0, + trigger_async_id: 0, + resource: f64::from_bits(crate::value::TAG_UNDEFINED), + args: Vec::new(), + }) + }); +} + #[cfg(test)] pub(crate) fn test_clear_queued_jobs() { QUEUED_MICROTASKS.with(|q| q.borrow_mut().clear()); diff --git a/crates/perry-runtime/src/builtins/mod.rs b/crates/perry-runtime/src/builtins/mod.rs index d11b316fc2..423a98b6be 100644 --- a/crates/perry-runtime/src/builtins/mod.rs +++ b/crates/perry-runtime/src/builtins/mod.rs @@ -191,6 +191,10 @@ pub use globals::{ pub(crate) use globals::{ discard_cancelled_microtasks, drain_queued_microtasks_count, queued_microtasks_pending, }; +#[cfg(perry_gc_instruments)] +pub(crate) use globals::snap_pending_ticks; +#[cfg(all(test, perry_gc_instruments))] +pub(crate) use globals::test_push_tick; /// #6943: inertness predicate for the `js_string_coerce`-as-property-key /// rooting family. Crate-internal — callers use it to skip a diff --git a/crates/perry-runtime/src/promise/combinators.rs b/crates/perry-runtime/src/promise/combinators.rs index b5a7603997..61f57f0259 100644 --- a/crates/perry-runtime/src/promise/combinators.rs +++ b/crates/perry-runtime/src/promise/combinators.rs @@ -561,9 +561,16 @@ static KEEP_PROMISE_ALL_SETTLED_ITERABLE: extern "C" fn(f64) -> *mut Promise = #[used(compiler)] static KEEP_PROMISE_ANY_ITERABLE: extern "C" fn(f64) -> *mut Promise = js_promise_any_iterable; +/// One scheduled resolution: the promise and its value. The instrumented +/// cohort's entry also names the init that queued it (SNAPK4 census). +#[cfg(not(perry_gc_instruments))] +pub(in crate::promise) type ScheduledResolve = (*mut Promise, f64); +#[cfg(perry_gc_instruments)] +pub(in crate::promise) type ScheduledResolve = (*mut Promise, f64, u32); + // Queue for scheduled promise resolutions crate::perry_thread_local! { - pub(in crate::promise) static SCHEDULED_RESOLVES: RefCell> = const { RefCell::new(Vec::new()) }; + pub(in crate::promise) static SCHEDULED_RESOLVES: RefCell> = const { RefCell::new(Vec::new()) }; } /// Schedule a promise to be resolved with a value when microtasks run @@ -571,7 +578,10 @@ crate::perry_thread_local! { #[no_mangle] pub extern "C" fn js_promise_schedule_resolve(promise: *mut Promise, value: f64) { SCHEDULED_RESOLVES.with(|q| { + #[cfg(not(perry_gc_instruments))] q.borrow_mut().push((promise, value)); + #[cfg(perry_gc_instruments)] + q.borrow_mut().push((promise, value, crate::snap_census::job_owner())); }); } @@ -581,7 +591,7 @@ pub(super) fn process_scheduled_resolves() -> i32 { loop { let item = SCHEDULED_RESOLVES.with(|q| q.borrow_mut().pop()); match item { - Some((promise, value)) => { + Some((promise, value, ..)) => { js_promise_resolve(promise, value); count += 1; } diff --git a/crates/perry-runtime/src/promise/mod.rs b/crates/perry-runtime/src/promise/mod.rs index a6d8b0a12a..25898626e7 100644 --- a/crates/perry-runtime/src/promise/mod.rs +++ b/crates/perry-runtime/src/promise/mod.rs @@ -43,6 +43,8 @@ mod rejection_loop_tests; mod resolving_function_kind_tests; #[cfg(all(test, perry_gc_instruments))] mod snap_pending_job_tests; +#[cfg(all(test, perry_gc_instruments))] +mod snap_pending_queue_tests; pub mod scanners; mod settled_step; pub use settled_step::js_async_step_resume_now; @@ -761,19 +763,36 @@ pub(crate) fn snap_pending_job_list() -> Option> { }) } +/// SNAPK3c/K4 census: a callback's code address (`JsFunctionInfo::code`), 0 +/// for no callback. #[cfg(perry_gc_instruments)] -fn snap_pending_job(task: &Task) -> SnapPendingJob { - fn code(callback: ClosurePtr) -> usize { - if callback.is_null() { - return 0; - } - let info = unsafe { (*callback).info }; - if info.is_null() { - 0 - } else { - unsafe { (*info).code as usize } - } +pub(crate) fn snap_closure_code(callback: ClosurePtr) -> usize { + if callback.is_null() { + return 0; } + let info = unsafe { (*callback).info }; + if info.is_null() { + 0 + } else { + unsafe { (*info).code as usize } + } +} + +/// SNAPK4 census: the scheduled resolutions in queue order, one +/// `(owner, code)` per entry: the init that queued it and 0 (a resolution runs +/// no callback; it settles its promise). None while the queue is borrowed. +#[cfg(perry_gc_instruments)] +pub(crate) fn snap_scheduled_resolves() -> Option> { + combinators::SCHEDULED_RESOLVES.with(|q| { + q.try_borrow() + .ok() + .map(|q| q.iter().map(|r| (r.2, 0)).collect()) + }) +} + +#[cfg(perry_gc_instruments)] +fn snap_pending_job(task: &Task) -> SnapPendingJob { + let code = snap_closure_code; let (kind, callback, owner) = match task { Task::Promise(promise, _, is_fulfilled, context) => { let callback = if promise.is_null() { diff --git a/crates/perry-runtime/src/promise/scanners.rs b/crates/perry-runtime/src/promise/scanners.rs index 8ee0119933..ac0bd7774f 100644 --- a/crates/perry-runtime/src/promise/scanners.rs +++ b/crates/perry-runtime/src/promise/scanners.rs @@ -100,7 +100,7 @@ pub fn scan_promise_roots_mut(visitor: &mut crate::gc::RuntimeRootVisitor<'_>) { // Scan SCHEDULED_RESOLVES entries super::combinators::SCHEDULED_RESOLVES.with(|q| { let mut q = q.borrow_mut(); - for (promise_ptr, value) in q.iter_mut() { + for (promise_ptr, value, ..) in q.iter_mut() { visitor.visit_raw_mut_ptr_slot(promise_ptr); visitor.visit_nanbox_f64_slot(value); } @@ -505,7 +505,7 @@ fn scan_scheduled_resolves_step( if !consume_root_work(remaining) { return false; } - let (promise_ptr, value) = &mut q[state.index]; + let (promise_ptr, value, ..) = &mut q[state.index]; match state.slot { 0 => visitor.visit_raw_mut_ptr_slot(promise_ptr), 1 => visitor.visit_nanbox_f64_slot(value), @@ -593,7 +593,10 @@ pub(crate) fn test_seed_promise_scanner_roots( super::combinators::SCHEDULED_RESOLVES.with(|q| { let mut q = q.borrow_mut(); q.clear(); + #[cfg(not(perry_gc_instruments))] q.push((promise_ptr, value)); + #[cfg(perry_gc_instruments)] + q.push((promise_ptr, value, 0)); }); } @@ -672,7 +675,7 @@ pub(crate) fn test_promise_scanner_snapshot() -> TestPromiseScannerSnapshot { }); super::combinators::SCHEDULED_RESOLVES.with(|q| { let q = q.borrow(); - if let Some((promise_ptr, value)) = q.first() { + if let Some((promise_ptr, value, ..)) = q.first() { snapshot.scheduled_promise_ptr = *promise_ptr as usize; snapshot.scheduled_value_bits = value.to_bits(); } diff --git a/crates/perry-runtime/src/promise/snap_pending_queue_tests.rs b/crates/perry-runtime/src/promise/snap_pending_queue_tests.rs new file mode 100644 index 0000000000..7af183b6af --- /dev/null +++ b/crates/perry-runtime/src/promise/snap_pending_queue_tests.rs @@ -0,0 +1,53 @@ +//! SNAPK4: the census names each queued nextTick job (`snap_pending_ticks`) +//! and each scheduled resolution (`snap_scheduled_resolves`) by the init that +//! queued it and its callback's code address, read from the queues themselves. +use super::*; + +extern "C" fn tick_a( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _v: f64, +) -> f64 { + 1.0 +} + +extern "C" fn tick_b( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _v: f64, +) -> f64 { + 2.0 +} + +#[test] +fn pending_ticks_name_owner_and_callback() { + let info_a = crate::fn_info!(tick_a, 1); + let info_b = crate::fn_info!(tick_b, 1); + let code_a = unsafe { (*info_a).code as usize }; + let code_b = unsafe { (*info_b).code as usize }; + assert_ne!(code_a, code_b); + let cb_a = crate::closure::js_closure_alloc(info_a, 0) as i64; + let cb_b = crate::closure::js_closure_alloc(info_b, 0) as i64; + crate::builtins::test_clear_queued_jobs(); + assert_eq!(crate::builtins::snap_pending_ticks(), Some(Vec::new()), "none pending, no entry"); + crate::builtins::test_push_tick(cb_a, 7); + crate::builtins::test_push_tick(cb_b, 9); + let ticks = crate::builtins::snap_pending_ticks().expect("queue not borrowed"); + crate::builtins::test_clear_queued_jobs(); + assert_eq!(ticks, vec![(7, code_a), (9, code_b)]); +} + +#[test] +fn scheduled_resolves_name_owner() { + combinators::SCHEDULED_RESOLVES.with(|q| q.borrow_mut().clear()); + assert_eq!(snap_scheduled_resolves(), Some(Vec::new()), "none pending, no entry"); + let promise = js_promise_new(); + combinators::SCHEDULED_RESOLVES.with(|q| { + let mut q = q.borrow_mut(); + q.push((promise, 1.0, 9)); + q.push((promise, 2.0, 12)); + }); + let resolves = snap_scheduled_resolves().expect("queue not borrowed"); + combinators::SCHEDULED_RESOLVES.with(|q| q.borrow_mut().clear()); + assert_eq!(resolves, vec![(9, 0), (12, 0)]); +} diff --git a/crates/perry-runtime/src/snap_census/runner.rs b/crates/perry-runtime/src/snap_census/runner.rs index d795bcd444..321b4a7ce1 100644 --- a/crates/perry-runtime/src/snap_census/runner.rs +++ b/crates/perry-runtime/src/snap_census/runner.rs @@ -5395,6 +5395,71 @@ fn pending_jobs_field() -> String { out.join(",") } +/// SNAPK4: the first 64 entries of a queue row's list field, `+N` counting +/// the rest; `busy` while the store is being changed. +fn shown_field(entries: Option>) -> (String, String) { + const SHOWN: usize = 64; + let Some(entries) = entries else { + return ("busy".to_owned(), "busy".to_owned()); + }; + let n = entries.len(); + let mut out: Vec = entries.into_iter().take(SHOWN).collect(); + if n > SHOWN { + out.push(format!("+{}", n - SHOWN)); + } + (n.to_string(), out.join(",")) +} + +/// SNAPK4 rows at a candidate cut for the run queues beside TASK_QUEUE, each +/// read from its own store, one row per non-empty store (SNAP_DESIGN +/// §2.10.21). The nextTick queue (`tick_queue`, `tick:owner:code`) and the +/// scheduled resolutions (`scheduled_resolves`, `resolve:owner:0`) are imaged +/// with the task queue by the same FIFO-prefix rule (`policy=image`). A timer, +/// immediate or native completion (`timer_queue`, +/// `kind:owner:code:delay_ms:repeat`) waits on the host clock or host I/O: +/// a blocker (`policy=STOP`). +fn k4_queue_rows(st: &mut State) { + let init = st.frames.last().map_or(0, |f| f.init); + let ticks = crate::builtins::snap_pending_ticks() + .map(|t| t.iter().map(|(o, c)| format!("tick:{o}:{c:x}")).collect()); + if ticks.as_ref().is_none_or(|t: &Vec| !t.is_empty()) { + let (n, list) = shown_field(ticks); + input_record::event( + st, + "tick_queue", + format!("init={init};pending={n};policy=image;ticks={list}"), + ); + } + let resolves = crate::promise::snap_scheduled_resolves() + .map(|r| r.iter().map(|(o, c)| format!("resolve:{o}:{c:x}")).collect()); + if resolves.as_ref().is_none_or(|r: &Vec| !r.is_empty()) { + let (n, list) = shown_field(resolves); + input_record::event( + st, + "scheduled_resolves", + format!("init={init};pending={n};policy=image;resolves={list}"), + ); + } + let timers = crate::timer::snap_pending_timers().map(|t| { + t.iter() + .map(|e| { + format!( + "{}:{}:{:x}:{}:{}", + e.kind, e.owner, e.code, e.delay_ms, e.repeat as u8 + ) + }) + .collect() + }); + if timers.as_ref().is_none_or(|t: &Vec| !t.is_empty()) { + let (n, list) = shown_field(timers); + input_record::event( + st, + "timer_queue", + format!("init={init};pending={n};policy=STOP;timers={list}"), + ); + } +} + /// SNAPK2b checks at a candidate cut, `init_enter` of the init just pushed /// (DESIGN27 §1.1, §1.2). Open frames: every open ancestor resumes at its /// flag test (R1: static dependency or eager entry import, before the @@ -5449,5 +5514,6 @@ fn k2b_enter_rows(st: &mut State) { ); } } + k4_queue_rows(st); } } diff --git a/crates/perry-runtime/src/timer.rs b/crates/perry-runtime/src/timer.rs index cc7f77e5c7..67ba96ce2a 100644 --- a/crates/perry-runtime/src/timer.rs +++ b/crates/perry-runtime/src/timer.rs @@ -32,6 +32,8 @@ mod async_lifecycle; mod mock; mod phases; mod store; +#[cfg(perry_gc_instruments)] +pub(crate) use store::snap_pending_timers; use crate::promise::{js_promise_new, Promise}; use async_lifecycle::enqueue_destroy_ids; diff --git a/crates/perry-runtime/src/timer/snap_pending_timer_tests.rs b/crates/perry-runtime/src/timer/snap_pending_timer_tests.rs new file mode 100644 index 0000000000..495ebcf705 --- /dev/null +++ b/crates/perry-runtime/src/timer/snap_pending_timer_tests.rs @@ -0,0 +1,77 @@ +//! SNAPK4: the census names each scheduled timer and immediate by its class, +//! the init that queued it, its callback's code address, its delay and whether +//! it repeats (`snap_pending_timers`), read from the store's own entries. +use super::*; +use std::time::Duration; + +extern "C" fn timer_a( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _v: f64, +) -> f64 { + 1.0 +} + +extern "C" fn timer_b( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _v: f64, +) -> f64 { + 2.0 +} + +fn entry(id: i64, class: Class, delay_ms: u64, callback: i64, owner: u32) -> Entry { + let mut e = Entry::callback( + id, + class, + Instant::now() + Duration::from_millis(delay_ms), + delay_ms, + callback, + f64::from_bits(crate::value::TAG_UNDEFINED), + Vec::new(), + crate::async_context::AsyncContextSnapshot::default(), + 0, + 0, + None, + ); + e.context.snap_init = owner; + e +} + +#[test] +fn pending_timers_name_kind_owner_callback_delay_and_repeat() { + reset_for_test(); + let info_a = crate::fn_info!(timer_a, 1); + let info_b = crate::fn_info!(timer_b, 1); + let code_a = unsafe { (*info_a).code as usize }; + let code_b = unsafe { (*info_b).code as usize }; + assert_ne!(code_a, code_b); + let cb_a = crate::closure::js_closure_alloc(info_a, 0) as i64; + let cb_b = crate::closure::js_closure_alloc(info_b, 0) as i64; + with_current(|timers| { + timers.insert_timer(entry(1, Class::Timeout, 25, cb_a, 5)); + timers.insert_check(entry(2, Class::Immediate, 0, cb_b, 6)); + timers.insert_timer(entry(3, Class::Interval, 40, cb_b, 8)); + }); + let pending = snap_pending_timers().expect("store not locked"); + reset_for_test(); + assert_eq!( + pending, + vec![ + SnapPendingTimer { kind: "timeout", owner: 5, code: code_a, delay_ms: 25, repeat: false }, + SnapPendingTimer { kind: "immediate", owner: 6, code: code_b, delay_ms: 0, repeat: false }, + SnapPendingTimer { kind: "interval", owner: 8, code: code_b, delay_ms: 40, repeat: true }, + ] + ); +} + +#[test] +fn no_pending_timer_no_entry() { + reset_for_test(); + with_current(|timers| { + timers.insert_timer(entry(1, Class::Timeout, 25, 0, 5)); + assert!(timers.test_find_by_id(1).is_some(), "the subject must hold the entry first"); + assert!(timers.remove_by_id(1, |_| true).is_some()); + }); + assert_eq!(snap_pending_timers(), Some(Vec::new())); +} diff --git a/crates/perry-runtime/src/timer/store.rs b/crates/perry-runtime/src/timer/store.rs index d2e730da7a..79717cb4da 100644 --- a/crates/perry-runtime/src/timer/store.rs +++ b/crates/perry-runtime/src/timer/store.rs @@ -140,6 +140,13 @@ impl Entry { trigger_async_id: u64, scheduled: Option, ) -> Self { + // SNAPK4 census: the entry names the init that queued it. + #[cfg(perry_gc_instruments)] + let context = { + let mut context = context; + context.snap_init = crate::snap_census::job_owner(); + context + }; Self { _scheduled: scheduled, id, @@ -213,7 +220,16 @@ impl Entry { callback: 0, js_handle: f64::from_bits(crate::value::TAG_UNDEFINED), args: Vec::new(), - context: crate::async_context::AsyncContextSnapshot::default(), + context: { + #[allow(unused_mut)] + let mut context = crate::async_context::AsyncContextSnapshot::default(); + // SNAPK4 census: the entry names the init that queued it. + #[cfg(perry_gc_instruments)] + { + context.snap_init = crate::snap_census::job_owner(); + } + context + }, async_id: 0, trigger_async_id: 0, heap_pos: NO_POS, @@ -1017,6 +1033,56 @@ pub(crate) fn any_pending() -> bool { with_current_existing(|t| t.any_pending()).unwrap_or(false) } +/// SNAPK4 census: one scheduled entry as the census names it, read from the +/// entry itself: its class, the init that queued it +/// (`AsyncContextSnapshot::snap_init`, stamped when the entry is built), its +/// callback's code address (0 for a promise timer, which runs no callback), its +/// delay and whether it repeats. +#[cfg(perry_gc_instruments)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct SnapPendingTimer { + pub kind: &'static str, + pub owner: u32, + pub code: usize, + pub delay_ms: u64, + pub repeat: bool, +} + +/// SNAPK4 census: the calling agent's live entries (timer heaps, the check +/// queue of immediates, the poll queues of native completions) in scheduling +/// order. None while the store is locked. +#[cfg(perry_gc_instruments)] +pub(crate) fn snap_pending_timers() -> Option> { + let agent = crate::agent::current_agent(); + let store = match STORE.try_lock() { + Ok(store) => store, + Err(std::sync::TryLockError::Poisoned(p)) => p.into_inner(), + Err(std::sync::TryLockError::WouldBlock) => return None, + }; + let Some(timers) = store.agents.get(&agent) else { + return Some(Vec::new()); + }; + let mut live: Vec<&Entry> = timers.slab.iter().flatten().collect(); + live.sort_by_key(|e| e.seq); + Some( + live.into_iter() + .map(|e| SnapPendingTimer { + kind: match e.class { + Class::Promise => "promise", + Class::Timeout => "timeout", + Class::Interval => "interval", + Class::Immediate => "immediate", + Class::Pending => "native", + }, + owner: e.context.snap_init, + code: crate::promise::snap_closure_code(e.callback as crate::promise::ClosurePtr), + delay_ms: e.delay_ms, + repeat: e.class == Class::Interval, + }) + .collect(), + ) +} + /// `PERRY_GC_CENSUS`: one row for the calling agent's timer store. pub(super) fn census_rows() -> Vec { match with_current_existing(|timers| timers.census()) { @@ -1037,3 +1103,7 @@ pub(super) fn reset_for_test() { #[cfg(test)] #[path = "store_tests.rs"] mod tests; + +#[cfg(all(test, perry_gc_instruments))] +#[path = "snap_pending_timer_tests.rs"] +mod snap_pending_timer_tests; diff --git a/scripts/snap_inputs/checkk2b.py b/scripts/snap_inputs/checkk2b.py index c44a2637ed..794411dea0 100644 --- a/scripts/snap_inputs/checkk2b.py +++ b/scripts/snap_inputs/checkk2b.py @@ -94,6 +94,28 @@ def entry_init(): need(r.get("pending") == str(len(js)), "job_queue jobs= does not list every pending job") need(all(len(j) == 3 and int(j[1]) not in (0, int(r["init"])) and int(j[2], 16) != 0 for j in js), "a job without its queueing init or callback") +elif name == "f14q": + # SNAPK4: the run queues beside TASK_QUEUE, each named from its own store. + # m.ts queues a nextTick, a util.aborted resolution, a timeout (25), an + # interval (40) and an immediate; n.ts's init_enter sees all of them. + def ents(rows, field): + return [e.split(":") for r in rows for e in r.get(field, "").split(",") if e and not e.startswith("+")] + tq = [kv(v) for v in vals("tick_queue")] + sr = [kv(v) for v in vals("scheduled_resolves")] + tm = [kv(v) for v in vals("timer_queue")] + info = f"tick={tq[:1]} resolves={sr[:1]} timers={tm[:1]}" + need(len(tq) >= 1 and all(r["policy"] == "image" for r in tq), "no tick_queue image row") + need(all(len(e) == 3 and e[0] == "tick" and int(e[1]) != 0 and int(e[2], 16) != 0 for e in ents(tq, "ticks")), + "a tick without its queueing init or callback") + need(len(sr) >= 1 and all(r["policy"] == "image" for r in sr), "no scheduled_resolves image row") + need(all(len(e) == 3 and e[0] == "resolve" and int(e[1]) != 0 for e in ents(sr, "resolves")), + "a scheduled resolve without its queueing init") + need(len(tm) >= 1 and all(r["policy"] == "STOP" for r in tm), "no timer_queue STOP row") + te = ents(tm, "timers") + need(all(len(e) == 5 and int(e[1]) != 0 for e in te), "a timer without its queueing init") + kinds = {(e[0], e[3], e[4]) for e in te if int(e[2], 16) != 0} + for k in (("timeout", "25", "0"), ("interval", "40", "1"), ("immediate", "0", "0")): + need(k in kinds, f"no {k[0]} entry with delay {k[1]} repeat {k[2]} and a callback") elif name in ("f13", "f13s"): rp = [kv(v) for v in vals("stack.runtime_pc")] m_init = inits("stack.runtime_pc") diff --git a/scripts/snap_inputs/fixtures/snapk2b/f14q/m.ts b/scripts/snap_inputs/fixtures/snapk2b/f14q/m.ts new file mode 100644 index 0000000000..309505cc4e --- /dev/null +++ b/scripts/snap_inputs/fixtures/snapk2b/f14q/m.ts @@ -0,0 +1,11 @@ +import { aborted } from "node:util"; +// SNAPK4: run-queue entries queued at module scope, still pending when n.ts starts. +const g = globalThis as any; +process.nextTick(() => { g.t = 1; }); +setTimeout(() => { g.o = 1; }, 25); +g.iv = setInterval(() => { g.v = 1; }, 40); +setImmediate(() => { g.i = 1; }); +const ac = new AbortController(); +aborted(ac.signal, ac).then(() => { g.a = 1; }); +ac.abort(); +export {}; diff --git a/scripts/snap_inputs/fixtures/snapk2b/f14q/main.ts b/scripts/snap_inputs/fixtures/snapk2b/f14q/main.ts new file mode 100644 index 0000000000..21855f5d2e --- /dev/null +++ b/scripts/snap_inputs/fixtures/snapk2b/f14q/main.ts @@ -0,0 +1,5 @@ +import "./m.ts"; +import "./n.ts"; +gc(); +clearInterval((globalThis as any).iv); +console.log("K4_F14Q", (globalThis as any).n); diff --git a/scripts/snap_inputs/fixtures/snapk2b/f14q/n.ts b/scripts/snap_inputs/fixtures/snapk2b/f14q/n.ts new file mode 100644 index 0000000000..a31d191868 --- /dev/null +++ b/scripts/snap_inputs/fixtures/snapk2b/f14q/n.ts @@ -0,0 +1,2 @@ +(globalThis as any).n = (globalThis as any).t === undefined ? "before" : "after"; +export {}; diff --git a/scripts/snap_inputs/fixtures/snapk2b/f14q/package.json b/scripts/snap_inputs/fixtures/snapk2b/f14q/package.json new file mode 100644 index 0000000000..2d405d906e --- /dev/null +++ b/scripts/snap_inputs/fixtures/snapk2b/f14q/package.json @@ -0,0 +1 @@ +{"name":"snapk2b-f14q","type":"module"}