diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index 2dcd94f75f..4269d4b74d 100644 --- a/cli/dts/lib.deno.unstable.d.ts +++ b/cli/dts/lib.deno.unstable.d.ts @@ -1312,6 +1312,17 @@ declare namespace Deno { atime: number | Date, mtime: number | Date, ): Promise; + + /** *UNSTABLE**: new API, yet to be vetted. + * + * SleepSync puts the main thread to sleep synchronously for a given amount of + * time in milliseconds. + * + * ```ts + * Deno.sleepSync(10); + * ``` + */ + export function sleepSync(millis: number): Promise; } declare function fetch( diff --git a/cli/ops/timers.rs b/cli/ops/timers.rs index 74edc7267e..841cdf289d 100644 --- a/cli/ops/timers.rs +++ b/cli/ops/timers.rs @@ -25,6 +25,7 @@ use std::cell::RefCell; use std::future::Future; use std::pin::Pin; use std::rc::Rc; +use std::thread::sleep; use std::time::Duration; use std::time::Instant; @@ -77,6 +78,7 @@ pub fn init(rt: &mut deno_core::JsRuntime) { super::reg_json_sync(rt, "op_global_timer_start", op_global_timer_start); super::reg_json_async(rt, "op_global_timer", op_global_timer); super::reg_json_sync(rt, "op_now", op_now); + super::reg_json_sync(rt, "op_sleep_sync", op_sleep_sync); } fn op_global_timer_stop( @@ -157,3 +159,19 @@ fn op_now( "subsecNanos": subsec_nanos, })) } + +#[derive(Deserialize)] +struct SleepArgs { + millis: u64, +} + +fn op_sleep_sync( + state: &mut OpState, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + super::check_unstable(state, "Deno.sleepSync"); + let args: SleepArgs = serde_json::from_value(args)?; + sleep(Duration::from_millis(args.millis)); + Ok(json!({})) +} diff --git a/cli/rt/11_timers.js b/cli/rt/11_timers.js index 8f6a7e0497..c762c59d83 100644 --- a/cli/rt/11_timers.js +++ b/cli/rt/11_timers.js @@ -20,6 +20,10 @@ return core.jsonOpSync("op_now"); } + function sleepSync(millis = 0) { + return core.jsonOpSync("op_sleep_sync", { millis }); + } + // Derived from https://github.com/vadimg/js_bintrees. MIT Licensed. class RBNode { @@ -545,5 +549,6 @@ opStopGlobalTimer, opStartGlobalTimer, opNow, + sleepSync, }; })(this); diff --git a/cli/rt/90_deno_ns.js b/cli/rt/90_deno_ns.js index 3a41e6bd11..7e8598923a 100644 --- a/cli/rt/90_deno_ns.js +++ b/cli/rt/90_deno_ns.js @@ -80,6 +80,7 @@ __bootstrap.denoNs = { listen: __bootstrap.net.listen, connectTls: __bootstrap.tls.connectTls, listenTls: __bootstrap.tls.listenTls, + sleepSync: __bootstrap.timers.sleepSync, }; __bootstrap.denoNsUnstable = { diff --git a/cli/tests/unit/timers_test.ts b/cli/tests/unit/timers_test.ts index b304d89466..2d69ddb446 100644 --- a/cli/tests/unit/timers_test.ts +++ b/cli/tests/unit/timers_test.ts @@ -380,3 +380,55 @@ unitTest(async function timerIgnoresDateOverride(): Promise { } assertEquals(hasThrown, 1); }); + +unitTest({ perms: { hrtime: true } }, function sleepSync(): void { + const start = performance.now(); + Deno.sleepSync(10); + const after = performance.now(); + assert(after - start >= 10); +}); + +unitTest( + { perms: { hrtime: true } }, + async function sleepSyncShorterPromise(): Promise { + const perf = performance; + const short = 5; + const long = 10; + + const start = perf.now(); + const p = sleepAsync(short).then(() => { + const after = perf.now(); + // pending promises should resolve after the main thread comes out of sleep + assert(after - start >= long); + }); + Deno.sleepSync(long); + + await p; + }, +); + +unitTest( + { perms: { hrtime: true } }, + async function sleepSyncLongerPromise(): Promise { + const perf = performance; + const short = 5; + const long = 10; + + const start = perf.now(); + const p = sleepAsync(long).then(() => { + const after = perf.now(); + // sleeping for less than the duration of a promise should have no impact + // on the resolution of that promise + assert(after - start >= long); + }); + Deno.sleepSync(short); + + await p; + }, +); + +function sleepAsync(delay: number): Promise { + return new Promise((resolve) => { + setTimeout(() => resolve(), delay); + }); +}