Skip to content

Commit

Permalink
Done
Browse files Browse the repository at this point in the history
  • Loading branch information
hansl committed Jan 22, 2025
1 parent 04bed66 commit 2f88425
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 144 deletions.
28 changes: 20 additions & 8 deletions core/engine/src/job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ impl JobExecutor for IdleJobExecutor {
pub struct SimpleJobExecutor {
promise_jobs: RefCell<VecDeque<PromiseJob>>,
async_jobs: RefCell<VecDeque<NativeAsyncJob>>,
timeout_jobs: RefCell<VecDeque<TimeoutJob>>,
timeout_jobs: RefCell<Vec<TimeoutJob>>,
}

impl Debug for SimpleJobExecutor {
Expand All @@ -556,24 +556,34 @@ 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),
}
}

fn run_jobs(&self, context: &mut Context) -> JsResult<()> {
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 {
Expand All @@ -596,5 +606,7 @@ impl JobExecutor for SimpleJobExecutor {
job.call(&mut context.borrow_mut())?;
}
}

Ok(())
}
}
18 changes: 2 additions & 16 deletions core/runtime/src/interval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,6 @@ impl IntervalInnerState {
}
}

/// A simple async function that yields once.
fn yield_async() -> impl std::future::Future<Output = ()> {
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(
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down
240 changes: 120 additions & 120 deletions core/runtime/src/interval/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,16 @@ impl HostHooks for TestClockHooks {
}
}

fn create_context(hooks: &'static TestClockHooks) -> Context {
fn create_context(hooks: Rc<TestClockHooks>) -> Context {
let mut context = ContextBuilder::default().host_hooks(hooks).build().unwrap();
interval::register(&mut context).unwrap();
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(
[
Expand All @@ -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(
[
Expand All @@ -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();
Expand All @@ -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.
Expand All @@ -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,
);
}

0 comments on commit 2f88425

Please sign in to comment.