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.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/AI-DISCLOSURE.md +13 -0
  3. data/CHANGELOG.md +45 -0
  4. data/COMPATIBILITY.md +235 -0
  5. data/Cargo.lock +1263 -0
  6. data/Cargo.toml +62 -0
  7. data/LICENSE +21 -0
  8. data/README.md +254 -0
  9. data/THIRD_PARTY_NOTICES.md +4501 -0
  10. data/crates/mini_racer_engine/Cargo.toml +25 -0
  11. data/crates/mini_racer_engine/build.rs +13 -0
  12. data/crates/mini_racer_engine/src/context.rs +802 -0
  13. data/crates/mini_racer_engine/src/control.rs +239 -0
  14. data/crates/mini_racer_engine/src/error.rs +88 -0
  15. data/crates/mini_racer_engine/src/fast_calls.rs +681 -0
  16. data/crates/mini_racer_engine/src/lib.rs +20 -0
  17. data/crates/mini_racer_engine/src/notify.rs +107 -0
  18. data/crates/mini_racer_engine/src/process/code_cache.rs +412 -0
  19. data/crates/mini_racer_engine/src/process/fork.rs +261 -0
  20. data/crates/mini_racer_engine/src/process/platform.rs +194 -0
  21. data/crates/mini_racer_engine/src/process/tasks.rs +96 -0
  22. data/crates/mini_racer_engine/src/process/watchdog.rs +185 -0
  23. data/crates/mini_racer_engine/src/process/workers.rs +165 -0
  24. data/crates/mini_racer_engine/src/process.rs +69 -0
  25. data/crates/mini_racer_engine/src/runner.rs +1560 -0
  26. data/crates/mini_racer_engine/src/serialize.rs +253 -0
  27. data/crates/mini_racer_engine/src/snapshot.rs +221 -0
  28. data/crates/mini_racer_engine/src/spin.rs +23 -0
  29. data/crates/mini_racer_engine/src/sys.rs +566 -0
  30. data/crates/mini_racer_sync/Cargo.toml +18 -0
  31. data/crates/mini_racer_sync/src/channel.rs +621 -0
  32. data/crates/mini_racer_sync/src/fork.rs +116 -0
  33. data/crates/mini_racer_sync/src/lib.rs +20 -0
  34. data/crates/mini_racer_sync/src/lock.rs +49 -0
  35. data/crates/mini_racer_sync/src/parker.rs +44 -0
  36. data/crates/mini_racer_sync/src/primitives.rs +10 -0
  37. data/crates/mini_racer_sync/src/tasks.rs +345 -0
  38. data/crates/mini_racer_sync/src/termination.rs +1234 -0
  39. data/crates/mini_racer_sync/src/workers.rs +283 -0
  40. data/crates/mini_racer_wire/Cargo.toml +18 -0
  41. data/crates/mini_racer_wire/src/decode.rs +926 -0
  42. data/crates/mini_racer_wire/src/lib.rs +67 -0
  43. data/crates/mini_racer_wire/src/marker.rs +43 -0
  44. data/crates/mini_racer_wire/src/text.rs +106 -0
  45. data/crates/mini_racer_wire/src/write.rs +187 -0
  46. data/ext/mini_racer_rs/Cargo.toml +26 -0
  47. data/ext/mini_racer_rs/build.rs +16 -0
  48. data/ext/mini_racer_rs/extconf.rb +78 -0
  49. data/ext/mini_racer_rs/src/context.rs +485 -0
  50. data/ext/mini_racer_rs/src/decode.rs +450 -0
  51. data/ext/mini_racer_rs/src/encode.rs +333 -0
  52. data/ext/mini_racer_rs/src/errors.rs +156 -0
  53. data/ext/mini_racer_rs/src/fork.rs +31 -0
  54. data/ext/mini_racer_rs/src/gc_root.rs +117 -0
  55. data/ext/mini_racer_rs/src/guard.rs +7 -0
  56. data/ext/mini_racer_rs/src/host.rs +339 -0
  57. data/ext/mini_racer_rs/src/lib.rs +44 -0
  58. data/ext/mini_racer_rs/src/options.rs +269 -0
  59. data/ext/mini_racer_rs/src/platform.rs +42 -0
  60. data/ext/mini_racer_rs/src/rb_raw.rs +368 -0
  61. data/ext/mini_racer_rs/src/snapshot.rs +149 -0
  62. data/ext/mini_racer_rs/v8-archives.sha256 +8 -0
  63. data/lib/mini_racer/shared.rb +420 -0
  64. data/lib/mini_racer/truffleruby.rb +439 -0
  65. data/lib/mini_racer/version.rb +6 -0
  66. data/lib/mini_racer.rb +3 -0
  67. data/lib/mini_racer_rs/internal_error.rb +7 -0
  68. data/lib/mini_racer_rs/version.rb +5 -0
  69. data/lib/mini_racer_rs/vocabulary.rb +105 -0
  70. data/lib/mini_racer_rs.rb +33 -0
  71. data/licenses/mini_racer.txt +21 -0
  72. data/licenses/rust/SOURCES.tsv +6 -0
  73. data/licenses/rust/std/COPYRIGHT +34 -0
  74. data/licenses/rust/std/COPYRIGHT-library.html +29357 -0
  75. data/licenses/rust/std/LICENSE-APACHE +176 -0
  76. data/licenses/rust/std/LICENSE-MIT +25 -0
  77. data/licenses/v8/SOURCES.tsv +20 -0
  78. data/licenses/v8/abseil-cpp/LICENSE +203 -0
  79. data/licenses/v8/chromium-build/LICENSE +27 -0
  80. data/licenses/v8/components.tsv +29 -0
  81. data/licenses/v8/dragonbox/LICENSE-Boost +23 -0
  82. data/licenses/v8/fast_float/LICENSE-MIT +27 -0
  83. data/licenses/v8/highway/LICENSE +371 -0
  84. data/licenses/v8/icu/LICENSE +568 -0
  85. data/licenses/v8/inspector_protocol/LICENSE +27 -0
  86. data/licenses/v8/libc++/LICENSE.TXT +311 -0
  87. data/licenses/v8/libc++abi/LICENSE.TXT +311 -0
  88. data/licenses/v8/llvm-libc/LICENSE.TXT +278 -0
  89. data/licenses/v8/rapidhash/LICENSE +34 -0
  90. data/licenses/v8/simdutf/LICENSE +18 -0
  91. data/licenses/v8/siphash/LICENSE +116 -0
  92. data/licenses/v8/utf8-decoder/LICENSE +19 -0
  93. data/licenses/v8/v8/LICENSE +61 -0
  94. data/licenses/v8/v8/LICENSE.fdlibm +6 -0
  95. data/licenses/v8/v8/LICENSE.strongtalk +29 -0
  96. data/licenses/v8/v8/LICENSE.v8 +26 -0
  97. 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
+ }