Skip to content

Commit ccf3021

Browse files
committed
Adjust CLI executor to handle new finalization jobs
1 parent 756631e commit ccf3021

File tree

4 files changed

+108
-24
lines changed

4 files changed

+108
-24
lines changed

cli/src/executor.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub(crate) struct Executor {
2020
async_jobs: RefCell<VecDeque<NativeAsyncJob>>,
2121
timeout_jobs: RefCell<BTreeMap<JsInstant, Vec<TimeoutJob>>>,
2222
generic_jobs: RefCell<VecDeque<GenericJob>>,
23+
finalization_registry_jobs: RefCell<VecDeque<NativeAsyncJob>>,
2324

2425
printer: SharedExternalPrinterLogger,
2526
}
@@ -31,6 +32,7 @@ impl Executor {
3132
async_jobs: RefCell::default(),
3233
timeout_jobs: RefCell::default(),
3334
generic_jobs: RefCell::default(),
35+
finalization_registry_jobs: RefCell::default(),
3436
printer,
3537
}
3638
}
@@ -96,6 +98,9 @@ impl JobExecutor for Executor {
9698
.push(job);
9799
}
98100
Job::GenericJob(job) => self.generic_jobs.borrow_mut().push_back(job),
101+
Job::FinalizationRegistryCleanupJob(job) => {
102+
self.finalization_registry_jobs.borrow_mut().push_back(job);
103+
}
99104
job => self.printer.print(format!("unsupported job type {job:?}")),
100105
}
101106
}
@@ -106,12 +111,17 @@ impl JobExecutor for Executor {
106111

107112
async fn run_jobs_async(self: Rc<Self>, context: &RefCell<&mut Context>) -> JsResult<()> {
108113
let mut group = FutureGroup::new();
114+
let mut fr_group = FutureGroup::new();
109115

110116
loop {
111117
for job in mem::take(&mut *self.async_jobs.borrow_mut()) {
112118
group.insert(job.call(context));
113119
}
114120

121+
for job in mem::take(&mut *self.finalization_registry_jobs.borrow_mut()) {
122+
fr_group.insert(job.call(context));
123+
}
124+
115125
if let Some(Err(e)) = future::poll_once(group.next()).await.flatten() {
116126
self.printer.print(uncaught_job_error(&e));
117127
}
@@ -120,7 +130,16 @@ impl JobExecutor for Executor {
120130
// event loop is cancelled almost immediately after the channel with
121131
// the reader gets closed.
122132
if self.is_empty() && group.is_empty() {
123-
return Ok(());
133+
// Run finalizers with a lower priority than every other type of
134+
// job.
135+
if let Some(Err(e)) = future::poll_once(fr_group.next()).await.flatten() {
136+
self.printer.print(uncaught_job_error(&e));
137+
}
138+
139+
// Finalizers could enqueue new jobs, so we cannot just exit.
140+
if self.is_empty() {
141+
return Ok(());
142+
}
124143
}
125144

126145
{

cli/src/main.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::logger::SharedExternalPrinterLogger;
1717
use async_channel::Sender;
1818
use boa_engine::JsValue;
1919
use boa_engine::error::JsErasedError;
20-
use boa_engine::job::{JobExecutor, NativeAsyncJob};
20+
use boa_engine::job::NativeAsyncJob;
2121
use boa_engine::{
2222
Context, JsError, Source,
2323
builtins::promise::PromiseState,
@@ -35,9 +35,7 @@ use color_eyre::{
3535
};
3636
use colored::Colorize;
3737
use debug::init_boa_debug_object;
38-
use futures_lite::future;
3938
use rustyline::{EditMode, Editor, config::Config, error::ReadlineError};
40-
use std::cell::RefCell;
4139
use std::time::{Duration, Instant};
4240
use std::{
4341
fs::OpenOptions,
@@ -671,8 +669,7 @@ fn main() -> Result<()> {
671669
});
672670
context.enqueue_job(eval_loop.into());
673671

674-
let result = future::block_on(executor.run_jobs_async(&RefCell::new(context)))
675-
.map_err(|e| e.into_erased(context));
672+
let result = context.run_jobs().map_err(|e| e.into_erased(context));
676673

677674
handle.join().expect("failed to join thread");
678675

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,89 @@
1-
use indoc::indoc;
1+
mod miri {
22

3-
use crate::{TestAction, run_test_actions};
3+
use indoc::indoc;
44

5-
#[test]
6-
fn finalization_registry_simple() {
7-
run_test_actions([
8-
TestAction::run(indoc! {r#"
5+
use crate::{TestAction, run_test_actions};
6+
7+
#[test]
8+
fn finalization_registry_simple() {
9+
run_test_actions([
10+
TestAction::run(indoc! {r#"
911
let counter = 0;
1012
const registry = new FinalizationRegistry(() => {
1113
counter++;
1214
});
1315
1416
registry.register(["foo"]);
1517
"#}),
16-
TestAction::assert_eq("counter", 0),
17-
TestAction::inspect_context(|_| boa_gc::force_collect()),
18-
TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()),
19-
TestAction::assert_eq("counter", 1),
20-
]);
18+
TestAction::assert_eq("counter", 0),
19+
TestAction::inspect_context(|_| boa_gc::force_collect()),
20+
TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()),
21+
// Callback should run at least once
22+
TestAction::assert_eq("counter", 1),
23+
]);
24+
}
25+
26+
#[test]
27+
fn finalization_registry_unregister() {
28+
run_test_actions([
29+
TestAction::run(indoc! {r#"
30+
let counter = 0;
31+
const registry = new FinalizationRegistry(() => {
32+
counter++;
33+
});
34+
35+
{
36+
let array = ["foo"];
37+
registry.register(array, undefined, array);
38+
registry.unregister(array);
39+
}
40+
41+
"#}),
42+
TestAction::assert_eq("counter", 0),
43+
TestAction::inspect_context(|_| boa_gc::force_collect()),
44+
TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()),
45+
// Callback shouldn't run
46+
TestAction::assert_eq("counter", 0),
47+
]);
48+
}
49+
50+
#[test]
51+
fn finalization_registry_held_value_handover() {
52+
run_test_actions([
53+
TestAction::run(indoc! {r#"
54+
let counter = 0;
55+
const registry = new FinalizationRegistry((value) => {
56+
counter += value.increment;
57+
});
58+
59+
registry.register(["foo"], { increment: 5 });
60+
"#}),
61+
TestAction::assert_eq("counter", 0),
62+
TestAction::inspect_context(|_| boa_gc::force_collect()),
63+
TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()),
64+
// Registry should handover the held value as argument
65+
TestAction::assert_eq("counter", 5),
66+
]);
67+
}
68+
69+
#[test]
70+
fn finalization_registry_unrelated_unregister_token() {
71+
run_test_actions([
72+
TestAction::run(indoc! {r#"
73+
let counter = 0;
74+
75+
const registry = new FinalizationRegistry((value) => {
76+
counter += 1;
77+
});
78+
79+
registry.register(["foo"], undefined, {});
80+
registry.unregister({});
81+
"#}),
82+
TestAction::assert_eq("counter", 0),
83+
TestAction::inspect_context(|_| boa_gc::force_collect()),
84+
TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()),
85+
// Object should not have been unregistered if the token is not the correct one
86+
TestAction::assert_eq("counter", 1),
87+
]);
88+
}
2189
}

core/engine/src/job.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -547,17 +547,17 @@ pub enum Job {
547547
///
548548
/// As described on the [spec's section about execution][execution],
549549
///
550-
/// > Because calling HostEnqueueFinalizationRegistryCleanupJob is optional,
551-
/// > registered objects in a FinalizationRegistry do not necessarily hold
552-
/// > that FinalizationRegistry live. Implementations may omit FinalizationRegistry
553-
/// > callbacks for any reason, e.g., if the FinalizationRegistry itself becomes
550+
/// > Because calling `HostEnqueueFinalizationRegistryCleanupJob` is optional,
551+
/// > registered objects in a `FinalizationRegistry` do not necessarily hold
552+
/// > that `FinalizationRegistry` live. Implementations may omit `FinalizationRegistry`
553+
/// > callbacks for any reason, e.g., if the `FinalizationRegistry` itself becomes
554554
/// > dead, or if the application is shutting down.
555555
///
556556
/// For this reason, it is recommended to exclude `FinalizationRegistry` cleanup
557-
/// jobs from any condition that returns from [`JobExecutor::run_jobs`].
557+
/// jobs from any condition that exits from [`JobExecutor::run_jobs`].
558558
///
559-
/// By the same token, it is recommended to execute [`FinalizationRegistryCleanubJob`]
560-
/// separately from all other enqueued [`NativeAsyncJob`]s, prioritizing the
559+
/// By the same token, it is recommended to execute `FinalizationRegistry` cleanup
560+
/// jobs separately from all other enqueued [`NativeAsyncJob`]s, prioritizing the
561561
/// execution of all other jobs if possible.
562562
///
563563
/// [spec]: https://tc39.es/ecma262/#sec-weakref-host-hooks

0 commit comments

Comments
 (0)