mini_racer_rs 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/AI-DISCLOSURE.md +13 -0
- data/CHANGELOG.md +45 -0
- data/COMPATIBILITY.md +235 -0
- data/Cargo.lock +1263 -0
- data/Cargo.toml +62 -0
- data/LICENSE +21 -0
- data/README.md +254 -0
- data/THIRD_PARTY_NOTICES.md +4501 -0
- data/crates/mini_racer_engine/Cargo.toml +25 -0
- data/crates/mini_racer_engine/build.rs +13 -0
- data/crates/mini_racer_engine/src/context.rs +802 -0
- data/crates/mini_racer_engine/src/control.rs +239 -0
- data/crates/mini_racer_engine/src/error.rs +88 -0
- data/crates/mini_racer_engine/src/fast_calls.rs +681 -0
- data/crates/mini_racer_engine/src/lib.rs +20 -0
- data/crates/mini_racer_engine/src/notify.rs +107 -0
- data/crates/mini_racer_engine/src/process/code_cache.rs +412 -0
- data/crates/mini_racer_engine/src/process/fork.rs +261 -0
- data/crates/mini_racer_engine/src/process/platform.rs +194 -0
- data/crates/mini_racer_engine/src/process/tasks.rs +96 -0
- data/crates/mini_racer_engine/src/process/watchdog.rs +185 -0
- data/crates/mini_racer_engine/src/process/workers.rs +165 -0
- data/crates/mini_racer_engine/src/process.rs +69 -0
- data/crates/mini_racer_engine/src/runner.rs +1560 -0
- data/crates/mini_racer_engine/src/serialize.rs +253 -0
- data/crates/mini_racer_engine/src/snapshot.rs +221 -0
- data/crates/mini_racer_engine/src/spin.rs +23 -0
- data/crates/mini_racer_engine/src/sys.rs +566 -0
- data/crates/mini_racer_sync/Cargo.toml +18 -0
- data/crates/mini_racer_sync/src/channel.rs +621 -0
- data/crates/mini_racer_sync/src/fork.rs +116 -0
- data/crates/mini_racer_sync/src/lib.rs +20 -0
- data/crates/mini_racer_sync/src/lock.rs +49 -0
- data/crates/mini_racer_sync/src/parker.rs +44 -0
- data/crates/mini_racer_sync/src/primitives.rs +10 -0
- data/crates/mini_racer_sync/src/tasks.rs +345 -0
- data/crates/mini_racer_sync/src/termination.rs +1234 -0
- data/crates/mini_racer_sync/src/workers.rs +283 -0
- data/crates/mini_racer_wire/Cargo.toml +18 -0
- data/crates/mini_racer_wire/src/decode.rs +926 -0
- data/crates/mini_racer_wire/src/lib.rs +67 -0
- data/crates/mini_racer_wire/src/marker.rs +43 -0
- data/crates/mini_racer_wire/src/text.rs +106 -0
- data/crates/mini_racer_wire/src/write.rs +187 -0
- data/ext/mini_racer_rs/Cargo.toml +26 -0
- data/ext/mini_racer_rs/build.rs +16 -0
- data/ext/mini_racer_rs/extconf.rb +78 -0
- data/ext/mini_racer_rs/src/context.rs +485 -0
- data/ext/mini_racer_rs/src/decode.rs +450 -0
- data/ext/mini_racer_rs/src/encode.rs +333 -0
- data/ext/mini_racer_rs/src/errors.rs +156 -0
- data/ext/mini_racer_rs/src/fork.rs +31 -0
- data/ext/mini_racer_rs/src/gc_root.rs +117 -0
- data/ext/mini_racer_rs/src/guard.rs +7 -0
- data/ext/mini_racer_rs/src/host.rs +339 -0
- data/ext/mini_racer_rs/src/lib.rs +44 -0
- data/ext/mini_racer_rs/src/options.rs +269 -0
- data/ext/mini_racer_rs/src/platform.rs +42 -0
- data/ext/mini_racer_rs/src/rb_raw.rs +368 -0
- data/ext/mini_racer_rs/src/snapshot.rs +149 -0
- data/ext/mini_racer_rs/v8-archives.sha256 +8 -0
- data/lib/mini_racer/shared.rb +420 -0
- data/lib/mini_racer/truffleruby.rb +439 -0
- data/lib/mini_racer/version.rb +6 -0
- data/lib/mini_racer.rb +3 -0
- data/lib/mini_racer_rs/internal_error.rb +7 -0
- data/lib/mini_racer_rs/version.rb +5 -0
- data/lib/mini_racer_rs/vocabulary.rb +105 -0
- data/lib/mini_racer_rs.rb +33 -0
- data/licenses/mini_racer.txt +21 -0
- data/licenses/rust/SOURCES.tsv +6 -0
- data/licenses/rust/std/COPYRIGHT +34 -0
- data/licenses/rust/std/COPYRIGHT-library.html +29357 -0
- data/licenses/rust/std/LICENSE-APACHE +176 -0
- data/licenses/rust/std/LICENSE-MIT +25 -0
- data/licenses/v8/SOURCES.tsv +20 -0
- data/licenses/v8/abseil-cpp/LICENSE +203 -0
- data/licenses/v8/chromium-build/LICENSE +27 -0
- data/licenses/v8/components.tsv +29 -0
- data/licenses/v8/dragonbox/LICENSE-Boost +23 -0
- data/licenses/v8/fast_float/LICENSE-MIT +27 -0
- data/licenses/v8/highway/LICENSE +371 -0
- data/licenses/v8/icu/LICENSE +568 -0
- data/licenses/v8/inspector_protocol/LICENSE +27 -0
- data/licenses/v8/libc++/LICENSE.TXT +311 -0
- data/licenses/v8/libc++abi/LICENSE.TXT +311 -0
- data/licenses/v8/llvm-libc/LICENSE.TXT +278 -0
- data/licenses/v8/rapidhash/LICENSE +34 -0
- data/licenses/v8/simdutf/LICENSE +18 -0
- data/licenses/v8/siphash/LICENSE +116 -0
- data/licenses/v8/utf8-decoder/LICENSE +19 -0
- data/licenses/v8/v8/LICENSE +61 -0
- data/licenses/v8/v8/LICENSE.fdlibm +6 -0
- data/licenses/v8/v8/LICENSE.strongtalk +29 -0
- data/licenses/v8/v8/LICENSE.v8 +26 -0
- metadata +156 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
use crate::lock::{lock, wait};
|
|
2
|
+
use crate::primitives::{AtomicBool, AtomicU8, Condvar, Mutex, Ordering};
|
|
3
|
+
|
|
4
|
+
/// Where a runner is, as a fork sees it. Only `Running` keeps a fork waiting.
|
|
5
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
6
|
+
#[repr(u8)]
|
|
7
|
+
pub enum Phase {
|
|
8
|
+
Running = 0,
|
|
9
|
+
/// Idle, with its isolate in the control block and no lock held.
|
|
10
|
+
Deposited = 1,
|
|
11
|
+
/// Holding its isolate with no V8 call under way: waiting for a request,
|
|
12
|
+
/// inside a callback waiting for Ruby, or awaiting a promise until a task
|
|
13
|
+
/// is due.
|
|
14
|
+
Parked = 2,
|
|
15
|
+
/// Woken while a fork was pending, before touching its isolate, or
|
|
16
|
+
/// interrupted in JavaScript to wait the fork out.
|
|
17
|
+
ParkedForFork = 3,
|
|
18
|
+
Exited = 4,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
pub struct RunnerPhase(AtomicU8);
|
|
22
|
+
|
|
23
|
+
impl Default for RunnerPhase {
|
|
24
|
+
fn default() -> Self {
|
|
25
|
+
Self(AtomicU8::new(Phase::Running as u8))
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Both sides of the gate's handshake are read-modify-writes of the phase, so
|
|
30
|
+
// whichever comes second in its order sees the other: ordering that rests on
|
|
31
|
+
// a store and a load alone is beyond what loom checks.
|
|
32
|
+
impl RunnerPhase {
|
|
33
|
+
pub fn set(&self, phase: Phase) {
|
|
34
|
+
self.0.swap(phase as u8, Ordering::SeqCst);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// For a forker or a forked child; a runner knows its own phase.
|
|
38
|
+
pub fn get(&self) -> Phase {
|
|
39
|
+
match self.0.fetch_add(0, Ordering::SeqCst) {
|
|
40
|
+
1 => Phase::Deposited,
|
|
41
|
+
2 => Phase::Parked,
|
|
42
|
+
3 => Phase::ParkedForFork,
|
|
43
|
+
4 => Phase::Exited,
|
|
44
|
+
_ => Phase::Running,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Holds runners back from their isolates while a fork is pending.
|
|
50
|
+
///
|
|
51
|
+
/// A runner publishes `Running` and then reads `begun`, and `begin` sets
|
|
52
|
+
/// `begun` before the forker reads any phase, so either the runner sees the
|
|
53
|
+
/// fork and waits or the forker sees it `Running` and waits for it. Every runner resumes before every request, so the
|
|
54
|
+
/// common case takes no lock: one lock shared by all of a process's runners
|
|
55
|
+
/// cost three quarters of the call rate of four busy threads.
|
|
56
|
+
pub struct ForkGate {
|
|
57
|
+
begun: AtomicBool,
|
|
58
|
+
pending: Mutex<bool>,
|
|
59
|
+
released: Condvar,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
impl Default for ForkGate {
|
|
63
|
+
fn default() -> Self {
|
|
64
|
+
Self {
|
|
65
|
+
begun: AtomicBool::new(false),
|
|
66
|
+
pending: Mutex::new(false),
|
|
67
|
+
released: Condvar::new(),
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
impl ForkGate {
|
|
73
|
+
pub fn begin(&self) {
|
|
74
|
+
let mut pending = lock(&self.pending);
|
|
75
|
+
*pending = true;
|
|
76
|
+
self.begun.store(true, Ordering::SeqCst);
|
|
77
|
+
drop(pending);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
pub fn end(&self) {
|
|
81
|
+
let mut pending = lock(&self.pending);
|
|
82
|
+
*pending = false;
|
|
83
|
+
self.begun.store(false, Ordering::SeqCst);
|
|
84
|
+
drop(pending);
|
|
85
|
+
self.released.notify_all();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
pub fn resume(&self, phase: &RunnerPhase) {
|
|
89
|
+
phase.set(Phase::Running);
|
|
90
|
+
if !self.begun.load(Ordering::SeqCst) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
let mut pending = lock(&self.pending);
|
|
94
|
+
while *pending {
|
|
95
|
+
phase.set(Phase::ParkedForFork);
|
|
96
|
+
pending = wait(&self.released, pending);
|
|
97
|
+
}
|
|
98
|
+
phase.set(Phase::Running);
|
|
99
|
+
drop(pending);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Waits out a pending fork as `ParkedForFork`, for a runner interrupted
|
|
103
|
+
/// in JavaScript, which keeps its isolate meanwhile.
|
|
104
|
+
pub fn pause(&self, phase: &RunnerPhase) {
|
|
105
|
+
let mut pending = lock(&self.pending);
|
|
106
|
+
if !*pending {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
phase.set(Phase::ParkedForFork);
|
|
110
|
+
while *pending {
|
|
111
|
+
pending = wait(&self.released, pending);
|
|
112
|
+
}
|
|
113
|
+
phase.set(Phase::Running);
|
|
114
|
+
drop(pending);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#![forbid(unsafe_code)]
|
|
2
|
+
|
|
3
|
+
mod channel;
|
|
4
|
+
mod fork;
|
|
5
|
+
mod lock;
|
|
6
|
+
mod parker;
|
|
7
|
+
pub mod primitives;
|
|
8
|
+
mod tasks;
|
|
9
|
+
mod termination;
|
|
10
|
+
mod workers;
|
|
11
|
+
|
|
12
|
+
pub use channel::{Channel, ChannelError, Nesting, Notifier, Protocol, Side, ToRuby, ToV8};
|
|
13
|
+
pub use fork::{ForkGate, Phase, RunnerPhase};
|
|
14
|
+
pub use lock::{lock, wait, wait_timeout};
|
|
15
|
+
pub use tasks::TaskQueue;
|
|
16
|
+
pub use termination::{
|
|
17
|
+
DispatchId, DispatchKind, Halt, Inherited, Interrupt, RequestMark, TermCell, TermError,
|
|
18
|
+
TerminationCause, Terminator,
|
|
19
|
+
};
|
|
20
|
+
pub use workers::WorkerQueue;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
use std::sync::PoisonError;
|
|
2
|
+
use std::time::Duration;
|
|
3
|
+
|
|
4
|
+
use crate::primitives::{Condvar, Mutex, MutexGuard};
|
|
5
|
+
|
|
6
|
+
// Recovering is the poison policy: every critical section in a protocol type is
|
|
7
|
+
// a single write or take, so a panic cannot leave state half-updated, and
|
|
8
|
+
// failing instead would strand the Ruby thread waiting on the other side.
|
|
9
|
+
pub fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
|
|
10
|
+
mutex.lock().unwrap_or_else(PoisonError::into_inner)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
pub fn wait<'a, T>(condvar: &Condvar, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
|
|
14
|
+
condvar.wait(guard).unwrap_or_else(PoisonError::into_inner)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
pub fn wait_timeout<'a, T>(
|
|
18
|
+
condvar: &Condvar,
|
|
19
|
+
guard: MutexGuard<'a, T>,
|
|
20
|
+
timeout: Duration,
|
|
21
|
+
) -> MutexGuard<'a, T> {
|
|
22
|
+
condvar
|
|
23
|
+
.wait_timeout(guard, timeout)
|
|
24
|
+
.map_or_else(|poisoned| poisoned.into_inner().0, |(guard, _)| guard)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[cfg(test)]
|
|
28
|
+
#[cfg(not(loom))]
|
|
29
|
+
mod tests {
|
|
30
|
+
use super::*;
|
|
31
|
+
|
|
32
|
+
#[test]
|
|
33
|
+
#[expect(
|
|
34
|
+
clippy::significant_drop_tightening,
|
|
35
|
+
reason = "the guard must be held while the thread unwinds"
|
|
36
|
+
)]
|
|
37
|
+
fn a_panic_while_held_does_not_strand_later_lockers() {
|
|
38
|
+
let mutex = Mutex::new(0);
|
|
39
|
+
let unwound = std::panic::catch_unwind(|| {
|
|
40
|
+
let mut guard = lock(&mutex);
|
|
41
|
+
*guard = 1;
|
|
42
|
+
std::panic::resume_unwind(Box::new("unwinding while the lock is held"));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
assert!(unwound.is_err());
|
|
46
|
+
assert!(mutex.is_poisoned());
|
|
47
|
+
assert_eq!(*lock(&mutex), 1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
use std::time::Duration;
|
|
2
|
+
|
|
3
|
+
use crate::lock::{lock, wait, wait_timeout};
|
|
4
|
+
use crate::primitives::{Condvar, Mutex};
|
|
5
|
+
|
|
6
|
+
// A token parker: an unpark that arrives before the park is kept, so the waker
|
|
7
|
+
// never has to know whether the waiter is asleep yet.
|
|
8
|
+
pub struct Parker {
|
|
9
|
+
token: Mutex<bool>,
|
|
10
|
+
wake: Condvar,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
impl Default for Parker {
|
|
14
|
+
fn default() -> Self {
|
|
15
|
+
Self {
|
|
16
|
+
token: Mutex::new(false),
|
|
17
|
+
wake: Condvar::new(),
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl Parker {
|
|
23
|
+
pub fn park(&self, timeout: Option<Duration>) {
|
|
24
|
+
let mut token = lock(&self.token);
|
|
25
|
+
match timeout {
|
|
26
|
+
None => {
|
|
27
|
+
while !*token {
|
|
28
|
+
token = wait(&self.wake, token);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
Some(timeout) => {
|
|
32
|
+
if !*token {
|
|
33
|
+
token = wait_timeout(&self.wake, token, timeout);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
*token = false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pub fn unpark(&self) {
|
|
41
|
+
*lock(&self.token) = true;
|
|
42
|
+
self.wake.notify_one();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Protocol types name only these, so the loom models in tests/loom.rs check
|
|
2
|
+
// the same code the engine runs.
|
|
3
|
+
#[cfg(loom)]
|
|
4
|
+
pub use loom::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering};
|
|
5
|
+
#[cfg(loom)]
|
|
6
|
+
pub use loom::sync::{Condvar, Mutex, MutexGuard};
|
|
7
|
+
#[cfg(not(loom))]
|
|
8
|
+
pub use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering};
|
|
9
|
+
#[cfg(not(loom))]
|
|
10
|
+
pub use std::sync::{Condvar, Mutex, MutexGuard};
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
use std::collections::VecDeque;
|
|
2
|
+
use std::time::{Duration, Instant};
|
|
3
|
+
|
|
4
|
+
use crate::lock::lock;
|
|
5
|
+
use crate::parker::Parker;
|
|
6
|
+
use crate::primitives::Mutex;
|
|
7
|
+
|
|
8
|
+
// V8 asks for delays of seconds; anything longer is held to this, so every
|
|
9
|
+
// deadline fits the clock.
|
|
10
|
+
const LONGEST_DELAY: Duration = Duration::from_hours(365 * 24);
|
|
11
|
+
|
|
12
|
+
struct Entry<T> {
|
|
13
|
+
task: T,
|
|
14
|
+
nestable: bool,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
struct Delayed<T> {
|
|
18
|
+
due: Instant,
|
|
19
|
+
entry: Entry<T>,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pub struct Queue<T> {
|
|
23
|
+
ready: VecDeque<Entry<T>>,
|
|
24
|
+
// In posting order, which breaks ties between equal deadlines.
|
|
25
|
+
delayed: Vec<Delayed<T>>,
|
|
26
|
+
closed: bool,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl<T> Default for Queue<T> {
|
|
30
|
+
fn default() -> Self {
|
|
31
|
+
Self {
|
|
32
|
+
ready: VecDeque::new(),
|
|
33
|
+
delayed: Vec::new(),
|
|
34
|
+
closed: false,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
impl<T> Queue<T> {
|
|
40
|
+
pub fn push(&mut self, task: T, nestable: bool, delay: Duration) {
|
|
41
|
+
let entry = Entry { task, nestable };
|
|
42
|
+
if delay.is_zero() {
|
|
43
|
+
self.ready.push_back(entry);
|
|
44
|
+
} else {
|
|
45
|
+
let now = Instant::now();
|
|
46
|
+
let due = now.checked_add(delay.min(LONGEST_DELAY)).unwrap_or(now);
|
|
47
|
+
self.delayed.push(Delayed { due, entry });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pub fn pop(&mut self, now: Instant, nested: bool) -> Option<T> {
|
|
52
|
+
self.promote(now);
|
|
53
|
+
let index = self.position(nested)?;
|
|
54
|
+
self.ready.remove(index).map(|entry| entry.task)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Delayed tasks that have come due join the ready ones, earliest first.
|
|
58
|
+
fn promote(&mut self, now: Instant) {
|
|
59
|
+
if !self.delayed.iter().any(|delayed| delayed.due <= now) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
let (mut due, later): (Vec<_>, Vec<_>) = std::mem::take(&mut self.delayed)
|
|
63
|
+
.into_iter()
|
|
64
|
+
.partition(|delayed| delayed.due <= now);
|
|
65
|
+
due.sort_by_key(|delayed| delayed.due);
|
|
66
|
+
self.delayed = later;
|
|
67
|
+
self.ready
|
|
68
|
+
.extend(due.into_iter().map(|delayed| delayed.entry));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fn position(&self, nested: bool) -> Option<usize> {
|
|
72
|
+
self.ready
|
|
73
|
+
.iter()
|
|
74
|
+
.position(|entry| !nested || entry.nestable)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
pub fn next_due(&self) -> Option<Instant> {
|
|
78
|
+
self.delayed.iter().map(|delayed| delayed.due).min()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fn drain(&mut self) -> Vec<T> {
|
|
82
|
+
let mut delayed = std::mem::take(&mut self.delayed);
|
|
83
|
+
delayed.sort_by_key(|delayed| delayed.due);
|
|
84
|
+
std::mem::take(&mut self.ready)
|
|
85
|
+
.into_iter()
|
|
86
|
+
.chain(delayed.into_iter().map(|delayed| delayed.entry))
|
|
87
|
+
.map(|entry| entry.task)
|
|
88
|
+
.collect()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// One isolate's foreground tasks, as V8's own task runner keeps them: run in
|
|
93
|
+
/// posting order once due, and a non-nestable task never runs inside another.
|
|
94
|
+
///
|
|
95
|
+
/// Any thread may post. The runner pops and runs one task at a time, and
|
|
96
|
+
/// sleeps in `wait` until a task is due or `ring` is called, which is how a
|
|
97
|
+
/// termination reaches a runner awaiting a promise. A post or ring that lands
|
|
98
|
+
/// before the runner sleeps ends that sleep at once, so neither is lost.
|
|
99
|
+
pub struct TaskQueue<T> {
|
|
100
|
+
queue: Mutex<Queue<T>>,
|
|
101
|
+
doorbell: Parker,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
impl<T> Default for TaskQueue<T> {
|
|
105
|
+
fn default() -> Self {
|
|
106
|
+
Self {
|
|
107
|
+
queue: Mutex::new(Queue::default()),
|
|
108
|
+
doorbell: Parker::default(),
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
impl<T> TaskQueue<T> {
|
|
114
|
+
/// Queues `task` to run once `delay` has passed. A closed queue refuses
|
|
115
|
+
/// it, and the caller drops it.
|
|
116
|
+
pub fn post(&self, task: T, nestable: bool, delay: Duration) -> Result<(), T> {
|
|
117
|
+
let mut queue = lock(&self.queue);
|
|
118
|
+
if queue.closed {
|
|
119
|
+
drop(queue);
|
|
120
|
+
return Err(task);
|
|
121
|
+
}
|
|
122
|
+
queue.push(task, nestable, delay);
|
|
123
|
+
drop(queue);
|
|
124
|
+
self.doorbell.unpark();
|
|
125
|
+
Ok(())
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// The oldest task that is due. `nested`, for a pump inside a running
|
|
129
|
+
/// task, passes over non-nestable ones.
|
|
130
|
+
pub fn pop(&self, nested: bool) -> Option<T> {
|
|
131
|
+
let mut queue = lock(&self.queue);
|
|
132
|
+
let task = queue.pop(Instant::now(), nested);
|
|
133
|
+
drop(queue);
|
|
134
|
+
task
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// Sleeps until a task is due, a task is posted, or `ring` is called;
|
|
138
|
+
/// returns at once if a task is due already or the queue is closed. May
|
|
139
|
+
/// return early: callers check again. Only an outermost dispatch waits,
|
|
140
|
+
/// so every task counts.
|
|
141
|
+
pub fn wait(&self) {
|
|
142
|
+
let timeout = {
|
|
143
|
+
let mut queue = lock(&self.queue);
|
|
144
|
+
let now = Instant::now();
|
|
145
|
+
queue.promote(now);
|
|
146
|
+
if queue.closed || !queue.ready.is_empty() {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
queue
|
|
150
|
+
.next_due()
|
|
151
|
+
.map(|due| due.saturating_duration_since(now))
|
|
152
|
+
};
|
|
153
|
+
self.doorbell.park(timeout);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/// Ends a `wait`, or the next one if none is under way.
|
|
157
|
+
pub fn ring(&self) {
|
|
158
|
+
self.doorbell.unpark();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/// Refuses every later post and hands back what is queued, for its owner
|
|
162
|
+
/// to drop where the tasks belong.
|
|
163
|
+
pub fn close(&self) -> Vec<T> {
|
|
164
|
+
let mut queue = lock(&self.queue);
|
|
165
|
+
queue.closed = true;
|
|
166
|
+
let tasks = queue.drain();
|
|
167
|
+
drop(queue);
|
|
168
|
+
self.doorbell.unpark();
|
|
169
|
+
tasks
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// What a forked child takes over with an idle isolate: the queued tasks,
|
|
173
|
+
/// moved into a new queue and out of this one. `None` while the lock is
|
|
174
|
+
/// held, which in a child means by a thread that is gone.
|
|
175
|
+
#[must_use]
|
|
176
|
+
pub fn inherited(&self) -> Option<Self> {
|
|
177
|
+
let mut queue = self.queue.try_lock().ok()?;
|
|
178
|
+
let taken = Queue {
|
|
179
|
+
ready: std::mem::take(&mut queue.ready),
|
|
180
|
+
delayed: std::mem::take(&mut queue.delayed),
|
|
181
|
+
closed: queue.closed,
|
|
182
|
+
};
|
|
183
|
+
drop(queue);
|
|
184
|
+
Some(Self {
|
|
185
|
+
queue: Mutex::new(taken),
|
|
186
|
+
doorbell: Parker::default(),
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
impl<T> Drop for TaskQueue<T> {
|
|
192
|
+
// Tasks are dropped only by the thread their isolate runs on, which takes
|
|
193
|
+
// them with `close`. Any left here are leaked.
|
|
194
|
+
fn drop(&mut self) {
|
|
195
|
+
let leftover = lock(&self.queue).drain();
|
|
196
|
+
if !leftover.is_empty() {
|
|
197
|
+
#[expect(
|
|
198
|
+
clippy::mem_forget,
|
|
199
|
+
reason = "tasks may be dropped only where their isolate runs"
|
|
200
|
+
)]
|
|
201
|
+
std::mem::forget(leftover);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#[cfg(test)]
|
|
207
|
+
#[cfg(not(loom))]
|
|
208
|
+
mod tests {
|
|
209
|
+
use std::sync::Arc;
|
|
210
|
+
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
211
|
+
|
|
212
|
+
use super::*;
|
|
213
|
+
|
|
214
|
+
const NOW: Duration = Duration::ZERO;
|
|
215
|
+
|
|
216
|
+
fn drained(queue: &TaskQueue<u32>, nested: bool) -> Vec<u32> {
|
|
217
|
+
std::iter::from_fn(|| queue.pop(nested)).collect()
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
#[test]
|
|
221
|
+
fn runs_tasks_in_posting_order() {
|
|
222
|
+
let queue = TaskQueue::default();
|
|
223
|
+
for task in 0..4 {
|
|
224
|
+
queue.post(task, true, NOW).expect("open");
|
|
225
|
+
}
|
|
226
|
+
assert_eq!(drained(&queue, false), [0, 1, 2, 3]);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
#[test]
|
|
230
|
+
fn holds_a_delayed_task_until_it_is_due() {
|
|
231
|
+
let queue = TaskQueue::default();
|
|
232
|
+
queue
|
|
233
|
+
.post(1, true, Duration::from_millis(30))
|
|
234
|
+
.expect("open");
|
|
235
|
+
queue.post(2, true, NOW).expect("open");
|
|
236
|
+
assert_eq!(drained(&queue, false), [2]);
|
|
237
|
+
let started = Instant::now();
|
|
238
|
+
let mut waits = 0;
|
|
239
|
+
while queue.pop(false).is_none() {
|
|
240
|
+
queue.wait();
|
|
241
|
+
waits += 1;
|
|
242
|
+
}
|
|
243
|
+
assert!(started.elapsed() >= Duration::from_millis(25));
|
|
244
|
+
// The posts left the doorbell rung once; then one sleep to the deadline.
|
|
245
|
+
assert!(waits <= 3, "woke {waits} times");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// As V8's runner moves expired delayed tasks: behind those already ready,
|
|
249
|
+
// earliest deadline first.
|
|
250
|
+
#[test]
|
|
251
|
+
fn due_tasks_join_behind_ready_ones_by_deadline() {
|
|
252
|
+
let queue = TaskQueue::default();
|
|
253
|
+
queue
|
|
254
|
+
.post(1, true, Duration::from_millis(20))
|
|
255
|
+
.expect("open");
|
|
256
|
+
queue
|
|
257
|
+
.post(2, true, Duration::from_millis(10))
|
|
258
|
+
.expect("open");
|
|
259
|
+
queue
|
|
260
|
+
.post(3, true, Duration::from_millis(10))
|
|
261
|
+
.expect("open");
|
|
262
|
+
std::thread::sleep(Duration::from_millis(25));
|
|
263
|
+
queue.post(4, true, NOW).expect("open");
|
|
264
|
+
assert_eq!(drained(&queue, false), [4, 2, 3, 1]);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
#[test]
|
|
268
|
+
fn a_nested_pop_passes_over_non_nestable_tasks() {
|
|
269
|
+
let queue = TaskQueue::default();
|
|
270
|
+
queue.post(1, false, NOW).expect("open");
|
|
271
|
+
queue.post(2, true, NOW).expect("open");
|
|
272
|
+
assert_eq!(drained(&queue, true), [2]);
|
|
273
|
+
assert_eq!(drained(&queue, false), [1]);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
#[test]
|
|
277
|
+
fn close_hands_back_everything_and_refuses_later_posts() {
|
|
278
|
+
let queue = TaskQueue::default();
|
|
279
|
+
queue.post(1, true, Duration::from_secs(60)).expect("open");
|
|
280
|
+
queue.post(2, false, NOW).expect("open");
|
|
281
|
+
assert_eq!(queue.close(), [2, 1]);
|
|
282
|
+
assert_eq!(queue.post(3, true, NOW), Err(3));
|
|
283
|
+
assert_eq!(queue.pop(false), None);
|
|
284
|
+
queue.wait();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
#[test]
|
|
288
|
+
fn a_ring_ends_a_wait_with_nothing_due() {
|
|
289
|
+
let queue = Arc::new(TaskQueue::<u32>::default());
|
|
290
|
+
let ringer = Arc::clone(&queue);
|
|
291
|
+
let thread = std::thread::spawn(move || {
|
|
292
|
+
std::thread::sleep(Duration::from_millis(20));
|
|
293
|
+
ringer.ring();
|
|
294
|
+
});
|
|
295
|
+
queue.wait();
|
|
296
|
+
thread.join().expect("ringer");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
#[test]
|
|
300
|
+
fn an_absurd_delay_is_held_to_a_year() {
|
|
301
|
+
let queue = TaskQueue::default();
|
|
302
|
+
queue.post(1, true, Duration::MAX).expect("open");
|
|
303
|
+
assert_eq!(queue.pop(false), None);
|
|
304
|
+
assert_eq!(queue.close(), [1]);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
#[test]
|
|
308
|
+
fn a_child_takes_the_tasks_over() {
|
|
309
|
+
let queue = TaskQueue::default();
|
|
310
|
+
queue.post(1, true, NOW).expect("open");
|
|
311
|
+
queue.post(2, true, Duration::from_secs(60)).expect("open");
|
|
312
|
+
let inherited = queue.inherited().expect("unlocked");
|
|
313
|
+
assert_eq!(queue.pop(false), None);
|
|
314
|
+
assert_eq!(inherited.pop(false), Some(1));
|
|
315
|
+
assert_eq!(inherited.close(), [2]);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
#[test]
|
|
319
|
+
fn a_held_lock_leaves_nothing_to_take_over() {
|
|
320
|
+
let queue = TaskQueue::<u32>::default();
|
|
321
|
+
let held = lock(&queue.queue);
|
|
322
|
+
assert!(queue.inherited().is_none());
|
|
323
|
+
drop(held);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
struct Counted(Arc<AtomicUsize>);
|
|
327
|
+
|
|
328
|
+
impl Drop for Counted {
|
|
329
|
+
fn drop(&mut self) {
|
|
330
|
+
self.0.fetch_add(1, Ordering::SeqCst);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
#[test]
|
|
335
|
+
fn tasks_left_in_a_dropped_queue_are_leaked() {
|
|
336
|
+
let drops = Arc::new(AtomicUsize::new(0));
|
|
337
|
+
let queue = TaskQueue::default();
|
|
338
|
+
queue
|
|
339
|
+
.post(Counted(Arc::clone(&drops)), true, NOW)
|
|
340
|
+
.map_err(drop)
|
|
341
|
+
.expect("open");
|
|
342
|
+
drop(queue);
|
|
343
|
+
assert_eq!(drops.load(Ordering::SeqCst), 0);
|
|
344
|
+
}
|
|
345
|
+
}
|