Skip to content

Commit

Permalink
Propagate jobs into JobExecutor::run_jobs
Browse files Browse the repository at this point in the history
  • Loading branch information
jedel1043 committed Jan 14, 2025
1 parent a7c10d3 commit 9c35fcd
Show file tree
Hide file tree
Showing 19 changed files with 125 additions and 100 deletions.
18 changes: 10 additions & 8 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use boa_engine::{
optimizer::OptimizerOptions,
script::Script,
vm::flowgraph::{Direction, Graph},
Context, JsError, Source,
Context, JsError, JsResult, Source,
};
use boa_parser::source::ReadChar;
use clap::{Parser, ValueEnum, ValueHint};
Expand Down Expand Up @@ -292,7 +292,7 @@ fn evaluate_file(
);

let promise = module.load_link_evaluate(context);
context.run_jobs();
context.run_jobs().map_err(|err| err.into_erased(context))?;
let result = promise.state();

return match result {
Expand All @@ -308,9 +308,9 @@ fn evaluate_file(
Ok(v) => println!("{}", v.display()),
Err(v) => eprintln!("Uncaught {v}"),
}
context.run_jobs();

Ok(())
context
.run_jobs()
.map_err(|err| err.into_erased(context).into())
}

fn evaluate_files(args: &Opt, context: &mut Context, loader: &SimpleModuleLoader) {
Expand Down Expand Up @@ -425,7 +425,9 @@ fn main() -> Result<()> {
eprintln!("{}: {}", "Uncaught".red(), v.to_string().red());
}
}
context.run_jobs();
if let Err(err) = context.run_jobs() {
eprintln!("{err}");
};
}
}

Expand Down Expand Up @@ -467,10 +469,10 @@ impl JobExecutor for Executor {
}
}

fn run_jobs(&self, context: &mut Context) {
fn run_jobs(&self, context: &mut Context) -> JsResult<()> {
loop {
if self.promise_jobs.borrow().is_empty() && self.async_jobs.borrow().is_empty() {
return;
return Ok(());
}

let jobs = std::mem::take(&mut *self.promise_jobs.borrow_mut());
Expand Down
3 changes: 1 addition & 2 deletions core/engine/src/builtins/promise/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ fn promise() {
count += 1;
"#}),
TestAction::assert_eq("count", 2),
#[allow(clippy::redundant_closure_for_method_calls)]
TestAction::inspect_context(|ctx| ctx.run_jobs()),
TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()),
TestAction::assert_eq("count", 3),
]);
}
11 changes: 7 additions & 4 deletions core/engine/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,9 +477,10 @@ impl Context {

/// Runs all the jobs with the provided job executor.
#[inline]
pub fn run_jobs(&mut self) {
self.job_executor().run_jobs(self);
pub fn run_jobs(&mut self) -> JsResult<()> {
let result = self.job_executor().run_jobs(self);
self.clear_kept_objects();
result
}

/// Asynchronously runs all the jobs with the provided job executor.
Expand All @@ -490,11 +491,13 @@ impl Context {
/// specific handling of each [`JobExecutor`]. If you want to execute jobs concurrently, you must
/// provide a custom implementatin of `JobExecutor` to the context.
#[allow(clippy::future_not_send)]
pub async fn run_jobs_async(&mut self) {
self.job_executor()
pub async fn run_jobs_async(&mut self) -> JsResult<()> {
let result = self
.job_executor()
.run_jobs_async(&RefCell::new(self))
.await;
self.clear_kept_objects();
result
}

/// Abstract operation [`ClearKeptObjects`][clear].
Expand Down
34 changes: 19 additions & 15 deletions core/engine/src/job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ pub trait JobExecutor {
fn enqueue_job(&self, job: Job, context: &mut Context);

/// Runs all jobs in the executor.
fn run_jobs(&self, context: &mut Context);
fn run_jobs(&self, context: &mut Context) -> JsResult<()>;

/// Asynchronously runs all jobs in the executor.
///
Expand All @@ -395,7 +395,7 @@ pub trait JobExecutor {
fn run_jobs_async<'a, 'b, 'fut>(
&'a self,
context: &'b RefCell<&mut Context>,
) -> Pin<Box<dyn Future<Output = ()> + 'fut>>
) -> Pin<Box<dyn Future<Output = JsResult<()>> + 'fut>>
where
'a: 'fut,
'b: 'fut,
Expand Down Expand Up @@ -427,7 +427,9 @@ pub struct IdleJobExecutor;
impl JobExecutor for IdleJobExecutor {
fn enqueue_job(&self, _: Job, _: &mut Context) {}

fn run_jobs(&self, _: &mut Context) {}
fn run_jobs(&self, _: &mut Context) -> JsResult<()> {
Ok(())
}
}

/// A simple FIFO executor that bails on the first error.
Expand All @@ -438,7 +440,7 @@ impl JobExecutor for IdleJobExecutor {
/// To disable running promise jobs on the engine, see [`IdleJobExecutor`].
#[derive(Default)]
pub struct SimpleJobExecutor {
jobs: RefCell<VecDeque<PromiseJob>>,
promise_jobs: RefCell<VecDeque<PromiseJob>>,
async_jobs: RefCell<VecDeque<NativeAsyncJob>>,
}

Expand All @@ -459,36 +461,38 @@ impl SimpleJobExecutor {
impl JobExecutor for SimpleJobExecutor {
fn enqueue_job(&self, job: Job, _: &mut Context) {
match job {
Job::PromiseJob(p) => self.jobs.borrow_mut().push_back(p),
Job::PromiseJob(p) => self.promise_jobs.borrow_mut().push_back(p),
Job::AsyncJob(a) => self.async_jobs.borrow_mut().push_back(a),
}
}

fn run_jobs(&self, context: &mut Context) {
fn run_jobs(&self, context: &mut Context) -> JsResult<()> {
let context = RefCell::new(context);
loop {
let mut next_job = self.async_jobs.borrow_mut().pop_front();
while let Some(job) = next_job {
if pollster::block_on(job.call(&context)).is_err() {
if let Err(err) = pollster::block_on(job.call(&context)) {
self.async_jobs.borrow_mut().clear();
return;
self.promise_jobs.borrow_mut().clear();
return Err(err);
};
next_job = self.async_jobs.borrow_mut().pop_front();
}

// Yeah, I have no idea why Rust extends the lifetime of a `RefCell` that should be immediately
// dropped after calling `pop_front`.
let mut next_job = self.jobs.borrow_mut().pop_front();
let mut next_job = self.promise_jobs.borrow_mut().pop_front();
while let Some(job) = next_job {
if job.call(&mut context.borrow_mut()).is_err() {
self.jobs.borrow_mut().clear();
return;
if let Err(err) = job.call(&mut context.borrow_mut()) {
self.async_jobs.borrow_mut().clear();
self.promise_jobs.borrow_mut().clear();
return Err(err);
};
next_job = self.jobs.borrow_mut().pop_front();
next_job = self.promise_jobs.borrow_mut().pop_front();
}

if self.async_jobs.borrow().is_empty() && self.jobs.borrow().is_empty() {
return;
if self.async_jobs.borrow().is_empty() && self.promise_jobs.borrow().is_empty() {
return Ok(());
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions core/engine/src/object/builtins/jspromise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1143,14 +1143,14 @@ impl JsPromise {
/// // Uncommenting the following line would panic.
/// // context.run_jobs();
/// ```
pub fn await_blocking(&self, context: &mut Context) -> Result<JsValue, JsValue> {
pub fn await_blocking(&self, context: &mut Context) -> Result<JsValue, JsError> {
loop {
match self.state() {
PromiseState::Pending => {
context.run_jobs();
context.run_jobs()?;
}
PromiseState::Fulfilled(f) => break Ok(f),
PromiseState::Rejected(r) => break Err(r),
PromiseState::Rejected(r) => break Err(JsError::from_opaque(r)),
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions core/engine/src/tests/async_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ fn return_on_then_infinite_loop() {
});
g.return();
"#}),
TestAction::inspect_context(Context::run_jobs),
TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()),
TestAction::assert_eq("count", 100),
]);
}
Expand All @@ -71,7 +71,7 @@ fn return_on_then_single() {
});
let ret = g.return()
"#}),
TestAction::inspect_context(Context::run_jobs),
TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()),
TestAction::assert_eq("first", false),
TestAction::assert_with_op("ret", |ret, context| {
assert_promise_iter_value(&ret, &JsValue::undefined(), true, context);
Expand Down Expand Up @@ -104,7 +104,7 @@ fn return_on_then_queue() {
let second = g.next();
let ret = g.return();
"#}),
TestAction::inspect_context(Context::run_jobs),
TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()),
TestAction::assert_with_op("first", |first, context| {
assert_promise_iter_value(&first, &JsValue::from(1), false, context);
true
Expand Down
9 changes: 3 additions & 6 deletions core/engine/src/tests/iterators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ fn iterator_close_in_continue_before_jobs() {
actual.push("async fn end");
}();
"#}),
#[allow(clippy::redundant_closure_for_method_calls)]
TestAction::inspect_context(|ctx| ctx.run_jobs()),
TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()),
TestAction::assert(indoc! {r#"
arrayEquals(
actual,
Expand Down Expand Up @@ -110,8 +109,7 @@ fn async_iterator_close_in_continue_is_awaited() {
actual.push("async fn end");
}();
"#}),
#[allow(clippy::redundant_closure_for_method_calls)]
TestAction::inspect_context(|ctx| ctx.run_jobs()),
TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()),
TestAction::assert(indoc! {r#"
arrayEquals(
actual,
Expand Down Expand Up @@ -198,8 +196,7 @@ fn mixed_iterators_close_in_continue() {
actual.push("async fn end");
}();
"#}),
#[allow(clippy::redundant_closure_for_method_calls)]
TestAction::inspect_context(|ctx| ctx.run_jobs()),
TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()),
TestAction::assert(indoc! {r#"
arrayEquals(
actual,
Expand Down
2 changes: 1 addition & 1 deletion core/engine/src/tests/promise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn issue_2658() {
genTwo.next().then(v => { result2 = v; });
"#
}),
TestAction::inspect_context(|ctx| ctx.run_jobs()),
TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()),
TestAction::assert("!result1.done"),
TestAction::assert_eq("result1.value", 5),
TestAction::assert("!result2.done"),
Expand Down
2 changes: 1 addition & 1 deletion core/engine/tests/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn subdirectories() {
let module = boa_engine::Module::parse(source, None, &mut context).unwrap();
let result = module.load_link_evaluate(&mut context);

context.run_jobs();
context.run_jobs().unwrap();
match result.state() {
PromiseState::Pending => {}
PromiseState::Fulfilled(v) => {
Expand Down
2 changes: 1 addition & 1 deletion core/engine/tests/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fn test_json_module_from_str() {

let module = Module::parse(source, None, &mut context).unwrap();
let promise = module.load_link_evaluate(&mut context);
context.run_jobs();
context.run_jobs().unwrap();

match promise.state() {
PromiseState::Pending => {}
Expand Down
6 changes: 3 additions & 3 deletions core/interop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ fn into_js_module() {
let root_module = Module::parse(source, None, &mut context).unwrap();

let promise_result = root_module.load_link_evaluate(&mut context);
context.run_jobs();
context.run_jobs().unwrap();

// Checking if the final promise didn't return an error.
assert!(
Expand Down Expand Up @@ -617,7 +617,7 @@ fn can_throw_exception() {
let root_module = Module::parse(source, None, &mut context).unwrap();

let promise_result = root_module.load_link_evaluate(&mut context);
context.run_jobs();
context.run_jobs().unwrap();

// Checking if the final promise didn't return an error.
assert_eq!(
Expand Down Expand Up @@ -721,7 +721,7 @@ fn class() {
let root_module = Module::parse(source, None, &mut context).unwrap();

let promise_result = root_module.load_link_evaluate(&mut context);
context.run_jobs();
context.run_jobs().unwrap();

// Checking if the final promise didn't return an error.
assert!(
Expand Down
2 changes: 1 addition & 1 deletion core/interop/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ fn js_class_test() {
let root_module = Module::parse(source, None, &mut context).unwrap();

let promise_result = root_module.load_link_evaluate(&mut context);
context.run_jobs();
context.run_jobs().unwrap();

// Checking if the final promise didn't return an error.
assert!(
Expand Down
2 changes: 1 addition & 1 deletion core/interop/tests/embedded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ fn simple() {
)
.expect("failed to parse module");
let promise = module.load_link_evaluate(&mut context);
context.run_jobs();
context.run_jobs().unwrap();

match promise.state() {
PromiseState::Fulfilled(value) => {
Expand Down
12 changes: 6 additions & 6 deletions examples/src/bin/module_fetch_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ fn main() -> JsResult<()> {

// Important to call `Context::run_jobs`, or else all the futures and promises won't be
// pushed forward by the job queue.
context.run_jobs();
context.run_jobs()?;

match promise.state() {
// Our job queue guarantees that all promises and futures are finished after returning
Expand Down Expand Up @@ -204,23 +204,23 @@ impl JobExecutor for Queue {
}

// While the sync flavor of `run_jobs` will block the current thread until all the jobs have finished...
fn run_jobs(&self, context: &mut Context) {
smol::block_on(smol::LocalExecutor::new().run(self.run_jobs_async(&RefCell::new(context))));
fn run_jobs(&self, context: &mut Context) -> JsResult<()> {
smol::block_on(smol::LocalExecutor::new().run(self.run_jobs_async(&RefCell::new(context))))
}

// ...the async flavor won't, which allows concurrent execution with external async tasks.
fn run_jobs_async<'a, 'b, 'fut>(
&'a self,
context: &'b RefCell<&mut Context>,
) -> Pin<Box<dyn Future<Output = ()> + 'fut>>
) -> Pin<Box<dyn Future<Output = JsResult<()>> + 'fut>>
where
'a: 'fut,
'b: 'fut,
{
Box::pin(async move {
// Early return in case there were no jobs scheduled.
if self.promise_jobs.borrow().is_empty() && self.async_jobs.borrow().is_empty() {
return;
return Ok(());
}
let mut group = FutureGroup::new();
loop {
Expand All @@ -231,7 +231,7 @@ impl JobExecutor for Queue {
if self.promise_jobs.borrow().is_empty() {
let Some(result) = group.next().await else {
// Both queues are empty. We can exit.
return;
return Ok(());
};

if let Err(err) = result {
Expand Down
2 changes: 1 addition & 1 deletion examples/src/bin/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ fn main() -> Result<(), Box<dyn Error>> {
);

// Very important to push forward the job queue after queueing promises.
context.run_jobs();
context.run_jobs()?;

// Checking if the final promise didn't return an error.
match promise_result.state() {
Expand Down
Loading

0 comments on commit 9c35fcd

Please sign in to comment.