diff --git a/core/engine/src/job.rs b/core/engine/src/job.rs index cf3e78453f9..e5c9e2a975a 100644 --- a/core/engine/src/job.rs +++ b/core/engine/src/job.rs @@ -534,7 +534,7 @@ impl JobExecutor for IdleJobExecutor { pub struct SimpleJobExecutor { promise_jobs: RefCell>, async_jobs: RefCell>, - timeout_jobs: RefCell>, + timeout_jobs: RefCell>, } impl Debug for SimpleJobExecutor { @@ -556,7 +556,7 @@ impl JobExecutor for SimpleJobExecutor { match job { Job::PromiseJob(p) => self.promise_jobs.borrow_mut().push_back(p), Job::AsyncJob(a) => self.async_jobs.borrow_mut().push_back(a), - Job::TimeoutJob(t) => self.timeout_jobs.borrow_mut().push_back(t), + Job::TimeoutJob(t) => self.timeout_jobs.borrow_mut().push(t), } } @@ -564,16 +564,26 @@ impl JobExecutor for SimpleJobExecutor { let now = context.host_hooks().utc_now(); // Execute timeout jobs first. We do not execute them in a loop. - let mut borrowed = self.timeout_jobs.borrow_mut(); - let (mut retain, drain) = borrowed.drain(..).partition(|job| job.timeout <= now); - borrowed.append(&mut retain); - - for job in drain { - job.call(context)?; + self.timeout_jobs.borrow_mut().sort_by_key(|a| a.timeout); + + let i = self + .timeout_jobs + .borrow() + .iter() + .position(|job| job.timeout <= now); + if let Some(i) = i { + let jobs_to_run: Vec<_> = self.timeout_jobs.borrow_mut().drain(..=i).collect(); + for job in jobs_to_run { + job.call(context)?; + } } let context = RefCell::new(context); loop { + if self.promise_jobs.borrow().is_empty() && self.async_jobs.borrow().is_empty() { + break; + } + // Block on ALL async jobs running in the queue. We don't block on a single // job, but loop through them in context. futures_lite::future::block_on(async { @@ -596,5 +606,7 @@ impl JobExecutor for SimpleJobExecutor { job.call(&mut context.borrow_mut())?; } } + + Ok(()) } } diff --git a/core/runtime/src/interval.rs b/core/runtime/src/interval.rs index df307d1d245..6667a5b6dfe 100644 --- a/core/runtime/src/interval.rs +++ b/core/runtime/src/interval.rs @@ -56,20 +56,6 @@ impl IntervalInnerState { } } -/// A simple async function that yields once. -fn yield_async() -> impl std::future::Future { - let mut done = false; - std::future::poll_fn(move |cx| { - if done { - std::task::Poll::Ready(()) - } else { - done = true; - cx.waker().wake_by_ref(); - std::task::Poll::Pending - } - }) -} - /// Inner handler function for handling intervals and timeout. #[allow(clippy::too_many_arguments)] fn handle( @@ -130,7 +116,7 @@ pub fn set_timeout( .to_integer_or_infinity(context) .unwrap_or(IntegerOrInfinity::Integer(0)); // The spec converts the delay to a 32-bit signed integer. - let delay = delay.clamp_finite(0, u32::MAX) as u64; + let delay = u64::from(delay.clamp_finite(0, u32::MAX)); // Get ownership of rest arguments. let rest = rest.to_vec(); @@ -167,7 +153,7 @@ pub fn set_interval( .unwrap_or_default() .to_integer_or_infinity(context) .unwrap_or(IntegerOrInfinity::Integer(0)); - let delay = delay.clamp_finite(0, u32::MAX) as u64; + let delay = u64::from(delay.clamp_finite(0, u32::MAX)); // Get ownership of rest arguments. let rest = rest.to_vec(); diff --git a/core/runtime/src/interval/tests.rs b/core/runtime/src/interval/tests.rs index 80f3a939b54..8405422889d 100644 --- a/core/runtime/src/interval/tests.rs +++ b/core/runtime/src/interval/tests.rs @@ -32,7 +32,7 @@ impl HostHooks for TestClockHooks { } } -fn create_context(hooks: &'static TestClockHooks) -> Context { +fn create_context(hooks: Rc) -> Context { let mut context = ContextBuilder::default().host_hooks(hooks).build().unwrap(); interval::register(&mut context).unwrap(); context @@ -40,8 +40,8 @@ fn create_context(hooks: &'static TestClockHooks) -> Context { #[test] fn set_timeout_basic() { - static HOOKS: TestClockHooks = TestClockHooks::default(); - let context = &mut create_context(&HOOKS); + let clock = Rc::new(TestClockHooks::default()); + let context = &mut create_context(clock.clone()); run_test_actions_with( [ @@ -67,8 +67,10 @@ fn set_timeout_basic() { #[test] fn set_timeout_cancel() { - let clock = TestClockHooks::default(); - let context = &mut create_context(&clock); + let clock = Rc::new(TestClockHooks::default()); + let context = &mut create_context(clock.clone()); + let clock1 = clock.clone(); + let clock2 = clock.clone(); run_test_actions_with( [ @@ -79,11 +81,10 @@ fn set_timeout_cancel() { "#, ), TestAction::inspect_context(|ctx| { + let clock = clock1; let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); assert_eq!(called.as_boolean(), Some(false)); - eprintln!("1"); ctx.run_jobs().unwrap(); - eprintln!("2"); clock.forward(50); let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); @@ -92,6 +93,7 @@ fn set_timeout_cancel() { }), TestAction::run("clearTimeout(id);"), TestAction::inspect_context(|ctx| { + let clock = clock2; clock.forward(100); let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); // Should still be false, as it was cancelled. @@ -101,116 +103,114 @@ fn set_timeout_cancel() { context, ); } -// -// #[test] -// fn set_timeout_delay() { -// let context = &mut Context::default(); -// let clock = TestClock::new(0); -// interval::register_with_clock(context, clock.clone()).unwrap(); -// -// run_test_actions_with( -// [ -// TestAction::run( -// r#" -// called = false; -// setTimeout(() => { called = true; }, 100); -// "#, -// ), -// TestAction::inspect_context(move |ctx| { -// // As long as the clock isn't updated, `called` will always be false. -// for _ in 0..5 { -// let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); -// assert_eq!(called.as_boolean(), Some(false)); -// ctx.run_jobs().unwrap(); -// } -// -// // Move forward 50 milliseconds, `called` should still be false. -// clock.forward(50); -// ctx.run_jobs().unwrap(); -// let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); -// assert_eq!(called.as_boolean(), Some(false)); -// -// clock.forward(50); -// ctx.run_jobs().unwrap(); -// let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); -// assert_eq!(called.as_boolean(), Some(true)); -// }), -// ], -// context, -// ); -// } -// -// #[test] -// fn set_interval_delay() { -// let context = &mut Context::default(); -// let clock = TestClock::new(0); -// let clock1 = clock.clone(); // For the first test. -// let clock2 = clock.clone(); // For the first test. -// interval::register_with_clock(context, clock.clone()).unwrap(); -// -// run_test_actions_with( -// [ -// TestAction::run( -// r#" -// called = 0; -// id = setInterval(() => { called++; }, 100); -// "#, -// ), -// TestAction::inspect_context(|ctx| { -// let clock = clock1; -// // As long as the clock isn't updated, `called` will always be 0. -// for _ in 0..5 { -// let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); -// assert_eq!(called.as_i32(), Some(0)); -// ctx.run_jobs().unwrap(); -// } -// -// // Move forward 50 milliseconds. -// clock.forward(50); -// ctx.run_jobs().unwrap(); -// let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); -// assert_eq!(called.as_i32(), Some(0)); -// -// // Move forward 50 milliseconds. -// clock.forward(50); -// ctx.run_jobs().unwrap(); -// let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); -// assert_eq!(called.as_i32(), Some(1)); -// -// // Move forward 50 milliseconds. -// clock.forward(50); -// ctx.run_jobs().unwrap(); -// let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); -// assert_eq!(called.as_i32(), Some(1)); -// -// // Move forward 50 milliseconds. -// clock.forward(50); -// ctx.run_jobs().unwrap(); -// let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); -// assert_eq!(called.as_i32(), Some(2)); -// -// // Move forward 500 milliseconds, should only be called once. -// clock.forward(500); -// ctx.run_jobs().unwrap(); -// let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); -// assert_eq!(called.as_i32(), Some(3)); -// }), -// // Cancel -// TestAction::run("clearInterval(id);"), -// TestAction::inspect_context(move |ctx| { -// let clock = clock2; -// // Doesn't matter how long, this should not be called ever again. -// clock.forward(500); -// ctx.run_jobs().unwrap(); -// let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); -// assert_eq!(called.as_i32(), Some(3)); -// -// clock.forward(500); -// ctx.run_jobs().unwrap(); -// let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); -// assert_eq!(called.as_i32(), Some(3)); -// }), -// ], -// context, -// ); -// } + +#[test] +fn set_timeout_delay() { + let clock = Rc::new(TestClockHooks::default()); + let context = &mut create_context(clock.clone()); + + run_test_actions_with( + [ + TestAction::run( + r#" + called = false; + setTimeout(() => { called = true; }, 100); + "#, + ), + TestAction::inspect_context(move |ctx| { + // As long as the clock isn't updated, `called` will always be false. + for _ in 0..5 { + let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); + assert_eq!(called.as_boolean(), Some(false)); + ctx.run_jobs().unwrap(); + } + + // Move forward 50 milliseconds, `called` should still be false. + clock.forward(50); + ctx.run_jobs().unwrap(); + let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); + assert_eq!(called.as_boolean(), Some(false)); + + clock.forward(50); + ctx.run_jobs().unwrap(); + let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); + assert_eq!(called.as_boolean(), Some(true)); + }), + ], + context, + ); +} + +#[test] +fn set_interval_delay() { + let clock = Rc::new(TestClockHooks::default()); + let context = &mut create_context(clock.clone()); + let clock1 = clock.clone(); // For the first test. + let clock2 = clock.clone(); // For the first test. + + run_test_actions_with( + [ + TestAction::run( + r#" + called = 0; + id = setInterval(() => { called++; }, 100); + "#, + ), + TestAction::inspect_context(|ctx| { + let clock = clock1; + // As long as the clock isn't updated, `called` will always be 0. + for _ in 0..5 { + let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); + assert_eq!(called.as_i32(), Some(0)); + ctx.run_jobs().unwrap(); + } + + // Move forward 50 milliseconds. + clock.forward(50); + ctx.run_jobs().unwrap(); + let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); + assert_eq!(called.as_i32(), Some(0)); + + // Move forward 50 milliseconds. + clock.forward(50); + ctx.run_jobs().unwrap(); + let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); + assert_eq!(called.as_i32(), Some(1)); + + // Move forward 50 milliseconds. + clock.forward(50); + ctx.run_jobs().unwrap(); + let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); + assert_eq!(called.as_i32(), Some(1)); + + // Move forward 50 milliseconds. + clock.forward(50); + ctx.run_jobs().unwrap(); + let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); + assert_eq!(called.as_i32(), Some(2)); + + // Move forward 500 milliseconds, should only be called once. + clock.forward(500); + ctx.run_jobs().unwrap(); + let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); + assert_eq!(called.as_i32(), Some(3)); + }), + // Cancel + TestAction::run("clearInterval(id);"), + TestAction::inspect_context(move |ctx| { + let clock = clock2; + // Doesn't matter how long, this should not be called ever again. + clock.forward(500); + ctx.run_jobs().unwrap(); + let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); + assert_eq!(called.as_i32(), Some(3)); + + clock.forward(500); + ctx.run_jobs().unwrap(); + let called = ctx.global_object().get(js_str!("called"), ctx).unwrap(); + assert_eq!(called.as_i32(), Some(3)); + }), + ], + context, + ); +}