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,261 @@
|
|
|
1
|
+
use std::cell::RefCell;
|
|
2
|
+
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
|
|
3
|
+
use std::sync::{Arc, Mutex, MutexGuard, OnceLock, Weak};
|
|
4
|
+
use std::time::{Duration, Instant};
|
|
5
|
+
|
|
6
|
+
use mini_racer_sync::{ForkGate, Phase, lock};
|
|
7
|
+
|
|
8
|
+
use crate::control::ControlBlock;
|
|
9
|
+
use crate::process::code_cache::CodeCache;
|
|
10
|
+
use crate::process::tasks::TaskRegistry;
|
|
11
|
+
use crate::process::watchdog::Watchdog;
|
|
12
|
+
use crate::process::workers::Workers;
|
|
13
|
+
|
|
14
|
+
// Long enough for any runner to answer and go idle, short enough that a fork
|
|
15
|
+
// that cannot be quiesced is merely slow.
|
|
16
|
+
const QUIESCE: Duration = Duration::from_millis(500);
|
|
17
|
+
const POLL: Duration = Duration::from_micros(200);
|
|
18
|
+
// A runner still running after this is interrupted to park in JavaScript.
|
|
19
|
+
const INTERRUPT_AFTER: Duration = Duration::from_millis(50);
|
|
20
|
+
// How long the worker threads get to finish the tasks they are running.
|
|
21
|
+
const PAUSE: Duration = Duration::from_millis(100);
|
|
22
|
+
|
|
23
|
+
// Forks since the engine's fork handlers were registered, counted by the child
|
|
24
|
+
// handler. A process is told apart from its ancestors by this, never by pid,
|
|
25
|
+
// which a descendant may get again once an ancestor has exited.
|
|
26
|
+
static EPOCH: AtomicU64 = AtomicU64::new(0);
|
|
27
|
+
|
|
28
|
+
/// Which process of a fork lineage this is.
|
|
29
|
+
#[must_use]
|
|
30
|
+
pub fn epoch() -> u64 {
|
|
31
|
+
EPOCH.load(Ordering::SeqCst)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// One process of a fork lineage. A child makes its own on first use and never
|
|
35
|
+
// locks or frees what it inherited, which a thread that is gone may hold.
|
|
36
|
+
pub struct Generation {
|
|
37
|
+
epoch: u64,
|
|
38
|
+
// Whether every fork that led to this process found its runners idle: a
|
|
39
|
+
// runner caught mid-change by any of them may have left V8's process-wide
|
|
40
|
+
// state locked for every process after it. True for the first process.
|
|
41
|
+
quiesced: bool,
|
|
42
|
+
prepared: AtomicBool,
|
|
43
|
+
gate: ForkGate,
|
|
44
|
+
// Held from the prepare handler to the parent handler, so forks from
|
|
45
|
+
// threads outside Ruby take turns with Ruby's.
|
|
46
|
+
forking: Mutex<()>,
|
|
47
|
+
blocks: Mutex<Vec<Weak<ControlBlock>>>,
|
|
48
|
+
// Per process, as a worker thread posting to it may have held its lock at
|
|
49
|
+
// the fork.
|
|
50
|
+
tasks: TaskRegistry,
|
|
51
|
+
watchdog: Mutex<Option<Arc<Watchdog>>>,
|
|
52
|
+
workers: Workers,
|
|
53
|
+
code_cache: CodeCache,
|
|
54
|
+
next: OnceLock<Box<Self>>,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static ROOT: OnceLock<Generation> = OnceLock::new();
|
|
58
|
+
|
|
59
|
+
impl Generation {
|
|
60
|
+
fn new(epoch: u64, quiesced: bool, code_cache: CodeCache) -> Self {
|
|
61
|
+
Self {
|
|
62
|
+
epoch,
|
|
63
|
+
quiesced,
|
|
64
|
+
prepared: AtomicBool::new(false),
|
|
65
|
+
gate: ForkGate::default(),
|
|
66
|
+
forking: Mutex::new(()),
|
|
67
|
+
blocks: Mutex::new(Vec::new()),
|
|
68
|
+
tasks: TaskRegistry::default(),
|
|
69
|
+
watchdog: Mutex::new(None),
|
|
70
|
+
workers: Workers::default(),
|
|
71
|
+
code_cache,
|
|
72
|
+
next: OnceLock::new(),
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pub const fn quiesced(&self) -> bool {
|
|
77
|
+
self.quiesced
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
pub(crate) const fn tasks(&self) -> &TaskRegistry {
|
|
81
|
+
&self.tasks
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
pub(crate) const fn watchdog(&self) -> &Mutex<Option<Arc<Watchdog>>> {
|
|
85
|
+
&self.watchdog
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
pub(crate) const fn workers(&self) -> &Workers {
|
|
89
|
+
&self.workers
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#[must_use]
|
|
93
|
+
pub const fn code_cache(&self) -> &CodeCache {
|
|
94
|
+
&self.code_cache
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
pub fn register(&self, block: &Arc<ControlBlock>) {
|
|
98
|
+
let mut blocks = lock(&self.blocks);
|
|
99
|
+
blocks.retain(|block| block.strong_count() > 0);
|
|
100
|
+
blocks.push(Arc::downgrade(block));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Called by a runner about to use its isolate: once a fork began, it
|
|
104
|
+
/// waits without the isolate until the fork is over.
|
|
105
|
+
pub fn resume(&self, block: &ControlBlock) {
|
|
106
|
+
self.gate.resume(&block.phase);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Waits out a pending fork; a runner interrupted in JavaScript holds its
|
|
110
|
+
/// isolate meanwhile, and the child leaves that context alone.
|
|
111
|
+
pub fn pause(&self, block: &ControlBlock) {
|
|
112
|
+
self.gate.pause(&block.phase);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
pub fn current() -> &'static Generation {
|
|
117
|
+
let epoch = epoch();
|
|
118
|
+
let mut generation = ROOT.get_or_init(|| Generation::new(epoch, true, CodeCache::default()));
|
|
119
|
+
while generation.epoch != epoch {
|
|
120
|
+
let quiesced = generation.quiesced && generation.prepared.load(Ordering::SeqCst);
|
|
121
|
+
let next = generation.epoch.saturating_add(1);
|
|
122
|
+
let parent = generation;
|
|
123
|
+
generation = parent.next.get_or_init(|| {
|
|
124
|
+
Box::new(Generation::new(
|
|
125
|
+
next,
|
|
126
|
+
quiesced,
|
|
127
|
+
parent.code_cache.inherited(),
|
|
128
|
+
))
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
generation
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Forks from Ruby under way, counted so nested `_fork` hooks and Ruby threads
|
|
135
|
+
// that move between native threads keep it right. Forks while it is zero run
|
|
136
|
+
// no Ruby in the child (spawn, system and backticks on macOS) and leave the
|
|
137
|
+
// runners be.
|
|
138
|
+
static INTENDED: AtomicUsize = AtomicUsize::new(0);
|
|
139
|
+
// Whether a fork from Ruby was under way when the latest fork was prepared.
|
|
140
|
+
// Its child starts its own count from this: only the forking thread carries on
|
|
141
|
+
// there, inside the one `_fork` hook it forked from, if any.
|
|
142
|
+
static FORK_INTENDED: AtomicBool = AtomicBool::new(false);
|
|
143
|
+
|
|
144
|
+
/// Marks the forks made until the matching `intend(false)` as ones whose child
|
|
145
|
+
/// runs Ruby and may use the contexts it inherits.
|
|
146
|
+
pub fn intend(forking: bool) {
|
|
147
|
+
if forking {
|
|
148
|
+
INTENDED.fetch_add(1, Ordering::SeqCst);
|
|
149
|
+
} else {
|
|
150
|
+
let mut count = INTENDED.load(Ordering::SeqCst);
|
|
151
|
+
while count > 0 {
|
|
152
|
+
match INTENDED.compare_exchange_weak(
|
|
153
|
+
count,
|
|
154
|
+
count.saturating_sub(1),
|
|
155
|
+
Ordering::SeqCst,
|
|
156
|
+
Ordering::SeqCst,
|
|
157
|
+
) {
|
|
158
|
+
Ok(_) => break,
|
|
159
|
+
Err(now) => count = now,
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// The fork this thread is preparing, so its parent handler alone reopens the
|
|
166
|
+
// gate. A child inherits its forking thread's turn, which belongs to the
|
|
167
|
+
// parent's generation.
|
|
168
|
+
type Turn = (&'static Generation, MutexGuard<'static, ()>);
|
|
169
|
+
|
|
170
|
+
// A turn taken in an ancestor process: its lock is left held, as the child
|
|
171
|
+
// releases nothing it inherited.
|
|
172
|
+
const fn abandon(turn: Turn) {
|
|
173
|
+
#[expect(
|
|
174
|
+
clippy::mem_forget,
|
|
175
|
+
reason = "the lock belongs to an ancestor's generation"
|
|
176
|
+
)]
|
|
177
|
+
std::mem::forget(turn);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
thread_local! {
|
|
181
|
+
static FORKING: RefCell<Option<Turn>> = const { RefCell::new(None) };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// pthread_atfork's prepare handler, on the forking thread inside fork() and
|
|
185
|
+
// after every Ruby `_fork` hook, so no Ruby code runs while runners are held.
|
|
186
|
+
// It waits until every runner is idle or parked, then pauses the worker
|
|
187
|
+
// threads until the parent handler and waits for the tasks they are running,
|
|
188
|
+
// so the child finds no isolate mid-change. No runner needs Ruby to get
|
|
189
|
+
// there: one waiting on a callback already counts as parked. Past the bounds
|
|
190
|
+
// the fork goes ahead anyway.
|
|
191
|
+
pub(crate) fn prepare() {
|
|
192
|
+
let intended = INTENDED.load(Ordering::SeqCst) != 0;
|
|
193
|
+
FORK_INTENDED.store(intended, Ordering::SeqCst);
|
|
194
|
+
if !intended {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
let generation = current();
|
|
198
|
+
let turn = lock(&generation.forking);
|
|
199
|
+
if let Some(stale) = FORKING.with(|forking| forking.borrow_mut().replace((generation, turn))) {
|
|
200
|
+
abandon(stale);
|
|
201
|
+
}
|
|
202
|
+
// Begun before the registry is read: a runner registered later sees it.
|
|
203
|
+
generation.gate.begin();
|
|
204
|
+
let blocks: Vec<Arc<ControlBlock>> = lock(&generation.blocks)
|
|
205
|
+
.iter()
|
|
206
|
+
.filter_map(Weak::upgrade)
|
|
207
|
+
.collect();
|
|
208
|
+
let started = Instant::now();
|
|
209
|
+
let mut interrupted = false;
|
|
210
|
+
let quiesced = loop {
|
|
211
|
+
let running: Vec<&Arc<ControlBlock>> = blocks
|
|
212
|
+
.iter()
|
|
213
|
+
.filter(|block| block.phase.get() == Phase::Running)
|
|
214
|
+
.collect();
|
|
215
|
+
if running.is_empty() {
|
|
216
|
+
break true;
|
|
217
|
+
}
|
|
218
|
+
let waited = started.elapsed();
|
|
219
|
+
if waited >= QUIESCE {
|
|
220
|
+
break false;
|
|
221
|
+
}
|
|
222
|
+
if !interrupted && waited >= INTERRUPT_AFTER {
|
|
223
|
+
interrupted = true;
|
|
224
|
+
for block in running {
|
|
225
|
+
block.interrupt_for_fork();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
std::thread::sleep(POLL);
|
|
229
|
+
};
|
|
230
|
+
let paused = generation.workers.pause(PAUSE);
|
|
231
|
+
generation
|
|
232
|
+
.prepared
|
|
233
|
+
.store(quiesced && paused, Ordering::SeqCst);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// pthread_atfork's parent handler, whether or not the fork succeeded.
|
|
237
|
+
pub(crate) fn parent() {
|
|
238
|
+
let Some((generation, turn)) = FORKING.with(|forking| forking.borrow_mut().take()) else {
|
|
239
|
+
return;
|
|
240
|
+
};
|
|
241
|
+
if generation.epoch != epoch() {
|
|
242
|
+
return abandon((generation, turn));
|
|
243
|
+
}
|
|
244
|
+
// Cleared before the gate opens, so no fork from another thread copies
|
|
245
|
+
// a prepared flag while runners move.
|
|
246
|
+
generation.prepared.store(false, Ordering::SeqCst);
|
|
247
|
+
generation.workers.resume();
|
|
248
|
+
generation.gate.end();
|
|
249
|
+
drop(turn);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// pthread_atfork's child handler: atomics only, which is all a child may touch
|
|
253
|
+
// before it execs or returns to Ruby. Everything else the child reads lazily
|
|
254
|
+
// from what it inherited.
|
|
255
|
+
pub(crate) fn child() {
|
|
256
|
+
EPOCH.fetch_add(1, Ordering::SeqCst);
|
|
257
|
+
INTENDED.store(
|
|
258
|
+
usize::from(FORK_INTENDED.load(Ordering::SeqCst)),
|
|
259
|
+
Ordering::SeqCst,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
use std::sync::atomic::{AtomicU8, Ordering};
|
|
2
|
+
use std::sync::{Mutex, MutexGuard, PoisonError};
|
|
3
|
+
|
|
4
|
+
use crate::FlagError;
|
|
5
|
+
|
|
6
|
+
const MAX_FLAG_BYTES: usize = 256;
|
|
7
|
+
|
|
8
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
9
|
+
pub enum Mode {
|
|
10
|
+
MultiThreaded,
|
|
11
|
+
SingleThreaded,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
struct Flags {
|
|
15
|
+
list: Vec<String>,
|
|
16
|
+
frozen: bool,
|
|
17
|
+
mode: Mode,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static FLAGS: Mutex<Flags> = Mutex::new(Flags {
|
|
21
|
+
list: Vec::new(),
|
|
22
|
+
frozen: false,
|
|
23
|
+
mode: Mode::MultiThreaded,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
fn flags() -> MutexGuard<'static, Flags> {
|
|
27
|
+
FLAGS.lock().unwrap_or_else(PoisonError::into_inner)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
struct Normalized {
|
|
31
|
+
flag: String,
|
|
32
|
+
mode: Option<Mode>,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fn normalize(name: &[u8], value: Option<&[u8]>) -> Result<Normalized, FlagError> {
|
|
36
|
+
if name.contains(&0) || value.is_some_and(|v| v.contains(&0)) {
|
|
37
|
+
return Err(FlagError::Nul);
|
|
38
|
+
}
|
|
39
|
+
let name = name.strip_prefix(b"--").unwrap_or(name);
|
|
40
|
+
let mut flag = Vec::with_capacity(name.len() + value.map_or(0, <[u8]>::len) + 3);
|
|
41
|
+
flag.extend_from_slice(b"--");
|
|
42
|
+
flag.extend_from_slice(name);
|
|
43
|
+
if let Some(value) = value {
|
|
44
|
+
flag.push(b'=');
|
|
45
|
+
flag.extend_from_slice(value);
|
|
46
|
+
}
|
|
47
|
+
if flag.len() >= MAX_FLAG_BYTES {
|
|
48
|
+
return Err(FlagError::TooLong);
|
|
49
|
+
}
|
|
50
|
+
let squashed: Vec<u8> = flag
|
|
51
|
+
.iter()
|
|
52
|
+
.copied()
|
|
53
|
+
.filter(|b| !matches!(b, b'-' | b'_'))
|
|
54
|
+
.collect();
|
|
55
|
+
let mode = match squashed.as_slice() {
|
|
56
|
+
b"singlethreaded" => Some(Mode::SingleThreaded),
|
|
57
|
+
b"nosinglethreaded" => Some(Mode::MultiThreaded),
|
|
58
|
+
_ => None,
|
|
59
|
+
};
|
|
60
|
+
Ok(Normalized {
|
|
61
|
+
flag: String::from_utf8_lossy(&flag).into_owned(),
|
|
62
|
+
mode,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub fn push_flag(name: &[u8], value: Option<&[u8]>) -> Result<(), FlagError> {
|
|
67
|
+
let normalized = normalize(name, value)?;
|
|
68
|
+
let mut flags = flags();
|
|
69
|
+
if flags.frozen {
|
|
70
|
+
return Err(FlagError::AlreadyInitialized);
|
|
71
|
+
}
|
|
72
|
+
if let Some(mode) = normalized.mode {
|
|
73
|
+
flags.mode = mode;
|
|
74
|
+
}
|
|
75
|
+
flags.list.push(normalized.flag);
|
|
76
|
+
drop(flags);
|
|
77
|
+
Ok(())
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// The mode once frozen, readable without the flags lock, which runner threads
|
|
81
|
+
// must not take: a fork that does not wait for them could leave it held.
|
|
82
|
+
static FROZEN_MODE: AtomicU8 = AtomicU8::new(0);
|
|
83
|
+
|
|
84
|
+
#[must_use]
|
|
85
|
+
pub fn mode() -> Mode {
|
|
86
|
+
match FROZEN_MODE.load(Ordering::Acquire) {
|
|
87
|
+
1 => Mode::MultiThreaded,
|
|
88
|
+
2 => Mode::SingleThreaded,
|
|
89
|
+
_ => flags().mode,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#[must_use]
|
|
94
|
+
pub fn is_initialized() -> bool {
|
|
95
|
+
flags().frozen
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// The last --stack_size (or --stack-size) value, in KiB, as V8 parses it.
|
|
99
|
+
#[must_use]
|
|
100
|
+
pub fn stack_size_kb() -> Option<usize> {
|
|
101
|
+
flags().list.iter().rev().find_map(|flag| {
|
|
102
|
+
let value = flag
|
|
103
|
+
.strip_prefix("--stack_size=")
|
|
104
|
+
.or_else(|| flag.strip_prefix("--stack-size="))?;
|
|
105
|
+
value.parse().ok()
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
pub(crate) fn freeze() -> Vec<String> {
|
|
110
|
+
let mut flags = flags();
|
|
111
|
+
flags.frozen = true;
|
|
112
|
+
let frozen = match flags.mode {
|
|
113
|
+
Mode::MultiThreaded => 1,
|
|
114
|
+
Mode::SingleThreaded => 2,
|
|
115
|
+
};
|
|
116
|
+
FROZEN_MODE.store(frozen, Ordering::Release);
|
|
117
|
+
flags.list.clone()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#[cfg(test)]
|
|
121
|
+
mod tests {
|
|
122
|
+
use super::*;
|
|
123
|
+
|
|
124
|
+
fn flag(name: &[u8], value: Option<&[u8]>) -> Result<String, FlagError> {
|
|
125
|
+
normalize(name, value).map(|n| n.flag)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#[test]
|
|
129
|
+
fn normalizes_like_upstream() {
|
|
130
|
+
assert_eq!(flag(b"use_strict", None).as_deref(), Ok("--use_strict"));
|
|
131
|
+
assert_eq!(flag(b"--use_strict", None).as_deref(), Ok("--use_strict"));
|
|
132
|
+
assert_eq!(
|
|
133
|
+
flag(b"stack_size", Some(b"512")).as_deref(),
|
|
134
|
+
Ok("--stack_size=512")
|
|
135
|
+
);
|
|
136
|
+
assert_eq!(flag(b"bad\0", None), Err(FlagError::Nul));
|
|
137
|
+
assert_eq!(flag(b"name", Some(b"v\0")), Err(FlagError::Nul));
|
|
138
|
+
assert!(flag(&[b'a'; 253], None).is_ok());
|
|
139
|
+
assert_eq!(flag(&[b'a'; 254], None), Err(FlagError::TooLong));
|
|
140
|
+
assert!(flag(&[b'a'; 250], Some(b"xx")).is_ok());
|
|
141
|
+
assert_eq!(flag(&[b'a'; 251], Some(b"xx")), Err(FlagError::TooLong));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#[test]
|
|
145
|
+
fn detects_single_threaded_spellings() {
|
|
146
|
+
for name in [
|
|
147
|
+
&b"single_threaded"[..],
|
|
148
|
+
b"--single-threaded",
|
|
149
|
+
b"singlethreaded",
|
|
150
|
+
] {
|
|
151
|
+
assert_eq!(
|
|
152
|
+
normalize(name, None).map(|n| n.mode),
|
|
153
|
+
Ok(Some(Mode::SingleThreaded))
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
for name in [
|
|
157
|
+
&b"no_single_threaded"[..],
|
|
158
|
+
b"--no-single-threaded",
|
|
159
|
+
b"nosingle_threaded",
|
|
160
|
+
] {
|
|
161
|
+
assert_eq!(
|
|
162
|
+
normalize(name, None).map(|n| n.mode),
|
|
163
|
+
Ok(Some(Mode::MultiThreaded))
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
assert_eq!(normalize(b"use_strict", None).map(|n| n.mode), Ok(None));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Flags freeze at the first initialization in a process, so this runs in
|
|
170
|
+
// a process of its own even where other tests share one.
|
|
171
|
+
#[test]
|
|
172
|
+
fn rejects_flags_after_initialization() {
|
|
173
|
+
const ALONE: &str = "MINI_RACER_RS_TEST_ALONE";
|
|
174
|
+
if std::env::var_os(ALONE).is_none() {
|
|
175
|
+
let status = std::process::Command::new(std::env::current_exe().expect("test binary"))
|
|
176
|
+
.args([
|
|
177
|
+
"process::platform::tests::rejects_flags_after_initialization",
|
|
178
|
+
"--exact",
|
|
179
|
+
])
|
|
180
|
+
.env(ALONE, "1")
|
|
181
|
+
.status()
|
|
182
|
+
.expect("run the test binary");
|
|
183
|
+
assert!(status.success(), "{status}");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
assert_eq!(push_flag(b"use_strict", None), Ok(()));
|
|
187
|
+
assert_eq!(freeze(), vec!["--use_strict".to_owned()]);
|
|
188
|
+
assert!(is_initialized());
|
|
189
|
+
assert_eq!(
|
|
190
|
+
push_flag(b"expose_gc", None),
|
|
191
|
+
Err(FlagError::AlreadyInitialized)
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
use std::ffi::c_void;
|
|
3
|
+
use std::panic::{AssertUnwindSafe, catch_unwind};
|
|
4
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
5
|
+
use std::sync::{Arc, Mutex};
|
|
6
|
+
use std::time::Duration;
|
|
7
|
+
|
|
8
|
+
use mini_racer_sync::{TaskQueue, lock};
|
|
9
|
+
|
|
10
|
+
use crate::process::fork;
|
|
11
|
+
|
|
12
|
+
pub type Tasks = TaskQueue<v8::Task>;
|
|
13
|
+
|
|
14
|
+
/// The queues V8's foreground tasks go to in one process, by the address V8
|
|
15
|
+
/// names each isolate by.
|
|
16
|
+
#[derive(Default)]
|
|
17
|
+
pub struct TaskRegistry {
|
|
18
|
+
queues: Mutex<HashMap<usize, Arc<Tasks>>>,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl TaskRegistry {
|
|
22
|
+
/// Sends the isolate's tasks to `tasks`. An entry a disposed isolate at the
|
|
23
|
+
/// same address left behind is replaced.
|
|
24
|
+
pub fn register(&self, isolate: usize, tasks: &Arc<Tasks>) {
|
|
25
|
+
lock(&self.queues).insert(isolate, Arc::clone(tasks));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Stops sending tasks to `tasks`, unless a newer isolate at the same
|
|
29
|
+
/// address has taken the entry over.
|
|
30
|
+
pub fn unregister(&self, isolate: usize, tasks: &Arc<Tasks>) {
|
|
31
|
+
let mut queues = lock(&self.queues);
|
|
32
|
+
if queues
|
|
33
|
+
.get(&isolate)
|
|
34
|
+
.is_some_and(|queued| Arc::ptr_eq(queued, tasks))
|
|
35
|
+
{
|
|
36
|
+
queues.remove(&isolate);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fn find(&self, isolate: usize) -> Option<Arc<Tasks>> {
|
|
41
|
+
lock(&self.queues).get(&isolate).cloned()
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Takes V8's foreground tasks off its platform, so a runner can sleep until
|
|
46
|
+
/// one is due and a termination can wake it.
|
|
47
|
+
pub struct Foreground;
|
|
48
|
+
|
|
49
|
+
impl v8::PlatformImpl for Foreground {
|
|
50
|
+
fn post_task(&self, isolate: *mut c_void, task: v8::Task) {
|
|
51
|
+
post(isolate, task, true, 0.0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fn post_non_nestable_task(&self, isolate: *mut c_void, task: v8::Task) {
|
|
55
|
+
post(isolate, task, false, 0.0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fn post_delayed_task(&self, isolate: *mut c_void, task: v8::Task, delay: f64) {
|
|
59
|
+
post(isolate, task, true, delay);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fn post_non_nestable_delayed_task(&self, isolate: *mut c_void, task: v8::Task, delay: f64) {
|
|
63
|
+
post(isolate, task, false, delay);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// The platform is built without idle task support, so V8 posts none.
|
|
67
|
+
fn post_idle_task(&self, _isolate: *mut c_void, task: v8::IdleTask) {
|
|
68
|
+
drop(task);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static UNSERVED: AtomicU64 = AtomicU64::new(0);
|
|
73
|
+
|
|
74
|
+
pub fn unserved() -> u64 {
|
|
75
|
+
UNSERVED.load(Ordering::Relaxed)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Called from any thread. A task for an isolate no runner serves (one being
|
|
79
|
+
// built into a snapshot, made or torn down) is dropped where it was posted,
|
|
80
|
+
// as nothing would ever run it.
|
|
81
|
+
fn post(isolate: *mut c_void, task: v8::Task, nestable: bool, delay: f64) {
|
|
82
|
+
let posted = catch_unwind(AssertUnwindSafe(|| {
|
|
83
|
+
let delay = Duration::try_from_secs_f64(delay).unwrap_or(Duration::MAX);
|
|
84
|
+
let Some(tasks) = fork::current().tasks().find(isolate.addr()) else {
|
|
85
|
+
UNSERVED.fetch_add(1, Ordering::Relaxed);
|
|
86
|
+
drop(task);
|
|
87
|
+
return;
|
|
88
|
+
};
|
|
89
|
+
if let Err(refused) = tasks.post(task, nestable, delay) {
|
|
90
|
+
drop(refused);
|
|
91
|
+
}
|
|
92
|
+
}));
|
|
93
|
+
if posted.is_err() {
|
|
94
|
+
std::process::abort();
|
|
95
|
+
}
|
|
96
|
+
}
|