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,185 @@
1
+ use std::cmp::{Ordering, Reverse};
2
+ use std::collections::BinaryHeap;
3
+ use std::sync::{Arc, Condvar, Mutex, Weak};
4
+ use std::time::{Duration, Instant};
5
+
6
+ use mini_racer_sync::{DispatchId, Interrupt, lock, wait, wait_timeout};
7
+
8
+ use crate::EngineError;
9
+ use crate::control::ControlBlock;
10
+
11
+ struct Entry {
12
+ at: Instant,
13
+ seq: u64,
14
+ block: Weak<ControlBlock>,
15
+ }
16
+
17
+ impl PartialEq for Entry {
18
+ fn eq(&self, other: &Self) -> bool {
19
+ (self.at, self.seq) == (other.at, other.seq)
20
+ }
21
+ }
22
+
23
+ impl Eq for Entry {}
24
+
25
+ impl PartialOrd for Entry {
26
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
27
+ Some(self.cmp(other))
28
+ }
29
+ }
30
+
31
+ impl Ord for Entry {
32
+ fn cmp(&self, other: &Self) -> Ordering {
33
+ (self.at, self.seq).cmp(&(other.at, other.seq))
34
+ }
35
+ }
36
+
37
+ #[derive(Default)]
38
+ struct Timers {
39
+ heap: BinaryHeap<Reverse<Entry>>,
40
+ seq: u64,
41
+ }
42
+
43
+ pub struct Watchdog {
44
+ timers: Mutex<Timers>,
45
+ changed: Condvar,
46
+ }
47
+
48
+ impl Watchdog {
49
+ fn push(&self, at: Instant, block: Weak<ControlBlock>) {
50
+ let mut timers = lock(&self.timers);
51
+ timers.seq = timers.seq.wrapping_add(1);
52
+ let seq = timers.seq;
53
+ timers.heap.push(Reverse(Entry { at, seq, block }));
54
+ drop(timers);
55
+ self.changed.notify_one();
56
+ }
57
+ }
58
+
59
+ // One thread per process serves every context's timeouts. It is kept per
60
+ // fork generation: a forked child has the parent's watchdog state but not its
61
+ // thread, and a runner of the parent may have held its lock at the fork.
62
+ fn current() -> Result<Arc<Watchdog>, EngineError> {
63
+ let mut slot = lock(super::fork::current().watchdog());
64
+ if let Some(watchdog) = slot.as_ref() {
65
+ return Ok(Arc::clone(watchdog));
66
+ }
67
+ let watchdog = Arc::new(Watchdog {
68
+ timers: Mutex::default(),
69
+ changed: Condvar::new(),
70
+ });
71
+ let serving = Arc::clone(&watchdog);
72
+ std::thread::Builder::new()
73
+ .name("mini_racer_rs watchdog".to_owned())
74
+ .spawn(move || run(&serving))
75
+ .map_err(|e| EngineError::Internal(format!("cannot start the timeout watchdog: {e}")))?;
76
+ *slot = Some(Arc::clone(&watchdog));
77
+ drop(slot);
78
+ Ok(watchdog)
79
+ }
80
+
81
+ /// Arms `dispatch`'s timeout. Fails rather than let a timed script run
82
+ /// unbounded when the watchdog cannot start.
83
+ // A context's timer slot holds its current deadline; a heap entry only wakes
84
+ // the watchdog to look at the slot, so a context needs a new entry only when
85
+ // its deadline moves earlier.
86
+ pub fn schedule(
87
+ after: Duration,
88
+ block: &Arc<ControlBlock>,
89
+ dispatch: DispatchId,
90
+ ) -> Result<(), EngineError> {
91
+ let watchdog = current()?;
92
+ let at = Instant::now()
93
+ .checked_add(after)
94
+ .ok_or_else(|| EngineError::Internal("timeout out of range".to_owned()))?;
95
+ let mut timer = lock(&block.timer);
96
+ timer.due = Some((at, dispatch));
97
+ let wake = timer.queued.is_none_or(|queued| at < queued);
98
+ if wake {
99
+ timer.queued = Some(at);
100
+ }
101
+ drop(timer);
102
+ // A wake entry the watchdog no longer needs is harmless: it rereads the slot.
103
+ if wake {
104
+ watchdog.push(at, Arc::downgrade(block));
105
+ }
106
+ Ok(())
107
+ }
108
+
109
+ /// Bounds work `dispatch` does after its timeout fired with the timeout again.
110
+ /// A deadline that has not fired yet already bounds it.
111
+ pub fn rearm_if_fired(
112
+ after: Duration,
113
+ block: &Arc<ControlBlock>,
114
+ dispatch: DispatchId,
115
+ ) -> Result<(), EngineError> {
116
+ let pending = lock(&block.timer)
117
+ .due
118
+ .is_some_and(|(_, due)| due == dispatch);
119
+ if pending {
120
+ return Ok(());
121
+ }
122
+ schedule(after, block, dispatch)
123
+ }
124
+
125
+ /// Forgets the context's pending timeout once its dispatch is over.
126
+ pub fn clear(block: &ControlBlock, dispatch: DispatchId) {
127
+ let mut timer = lock(&block.timer);
128
+ if timer.due.is_some_and(|(_, due)| due == dispatch) {
129
+ timer.due = None;
130
+ }
131
+ }
132
+
133
+ fn run(watchdog: &Watchdog) {
134
+ loop {
135
+ let mut timers = lock(&watchdog.timers);
136
+ let now = Instant::now();
137
+ match timers.heap.peek().map(|Reverse(entry)| entry.at) {
138
+ None => drop(wait(&watchdog.changed, timers)),
139
+ Some(at) if at > now => {
140
+ drop(wait_timeout(
141
+ &watchdog.changed,
142
+ timers,
143
+ at.saturating_duration_since(now),
144
+ ));
145
+ }
146
+ Some(_) => {
147
+ let mut due = Vec::new();
148
+ while timers
149
+ .heap
150
+ .peek()
151
+ .is_some_and(|Reverse(entry)| entry.at <= now)
152
+ {
153
+ if let Some(Reverse(entry)) = timers.heap.pop() {
154
+ due.push(entry.block);
155
+ }
156
+ }
157
+ // Lock order: the heap is released before any context's timer
158
+ // or termination cell is taken.
159
+ drop(timers);
160
+ for block in due.iter().filter_map(Weak::upgrade) {
161
+ fire(watchdog, &block, now);
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+
168
+ fn fire(watchdog: &Watchdog, block: &Arc<ControlBlock>, now: Instant) {
169
+ let mut timer = lock(&block.timer);
170
+ timer.queued = None;
171
+ let due = timer.due;
172
+ match due {
173
+ Some((at, _)) if at <= now => timer.due = None,
174
+ Some((at, _)) => timer.queued = Some(at),
175
+ None => {}
176
+ }
177
+ drop(timer);
178
+ match due {
179
+ Some((at, dispatch)) if at <= now => {
180
+ block.term.request(Interrupt::Timeout(dispatch));
181
+ }
182
+ Some((at, _)) => watchdog.push(at, Arc::downgrade(block)),
183
+ None => {}
184
+ }
185
+ }
@@ -0,0 +1,165 @@
1
+ use std::sync::{Once, OnceLock};
2
+ use std::time::Duration;
3
+
4
+ use mini_racer_sync::WorkerQueue;
5
+
6
+ use crate::process::platform::{self, Mode};
7
+ use crate::sys::WorkerTask;
8
+
9
+ // V8 and rusty_v8 hold a platform's pool to this many threads.
10
+ const MOST_THREADS: usize = 16;
11
+ // V8's own workers get the system's default thread stack, 8 MiB on Linux.
12
+ const MIN_STACK_BYTES: usize = 8 << 20;
13
+ const STACK_HEADROOM_BYTES: usize = 1 << 20;
14
+
15
+ #[derive(Clone, Copy)]
16
+ struct Size {
17
+ threads: usize,
18
+ stack_bytes: usize,
19
+ }
20
+
21
+ // Set before V8 initializes, so V8 sizes its jobs by this pool from the start,
22
+ // and read without the flags lock, which a fork may leave held.
23
+ static SIZE: OnceLock<Size> = OnceLock::new();
24
+
25
+ /// Sizes every process's pool as V8 would have sized its own for the
26
+ /// platform mode: what `rusty_v8` makes of a request for the default, or the
27
+ /// one thread the single-threaded platform keeps.
28
+ pub fn configure() {
29
+ let threads = match platform::mode() {
30
+ Mode::SingleThreaded => 1,
31
+ Mode::MultiThreaded => crate::sys::processors_online().clamp(1, MOST_THREADS),
32
+ };
33
+ // V8 lets a task on a worker thread use `--stack_size` of its stack, as it
34
+ // lets a runner.
35
+ let stack_bytes = platform::stack_size_kb().map_or(MIN_STACK_BYTES, |kb| {
36
+ kb.saturating_mul(1024)
37
+ .saturating_add(STACK_HEADROOM_BYTES)
38
+ .max(MIN_STACK_BYTES)
39
+ });
40
+ let _ = SIZE.set(Size {
41
+ threads,
42
+ stack_bytes,
43
+ });
44
+ }
45
+
46
+ /// How many worker threads a process runs, which V8 reads as its platform's.
47
+ pub fn threads() -> usize {
48
+ SIZE.get().map_or(1, |size| size.threads)
49
+ }
50
+
51
+ /// Where V8's worker tasks go: the current process's pool. A child never
52
+ /// posts to its parent's, whose threads it does not have.
53
+ pub fn post(task: WorkerTask, delay: Duration) {
54
+ super::fork::current().workers().post(task, delay);
55
+ }
56
+
57
+ /// One process's worker threads, started at its first worker task.
58
+ pub struct Workers {
59
+ queue: WorkerQueue<WorkerTask>,
60
+ started: Once,
61
+ }
62
+
63
+ impl Default for Workers {
64
+ fn default() -> Self {
65
+ Self {
66
+ queue: WorkerQueue::default(),
67
+ started: Once::new(),
68
+ }
69
+ }
70
+ }
71
+
72
+ impl Workers {
73
+ fn post(&'static self, task: WorkerTask, delay: Duration) {
74
+ self.queue.post(task, delay);
75
+ self.started.call_once(|| self.start());
76
+ }
77
+
78
+ fn start(&'static self) {
79
+ let Some(&Size {
80
+ threads,
81
+ stack_bytes,
82
+ }) = SIZE.get()
83
+ else {
84
+ return;
85
+ };
86
+ for _ in 0..threads {
87
+ let spawned = std::thread::Builder::new()
88
+ .name("mini_racer_rs worker".to_owned())
89
+ .stack_size(stack_bytes)
90
+ .spawn(move || {
91
+ loop {
92
+ self.queue.run_next(WorkerTask::run);
93
+ }
94
+ });
95
+ if spawned.is_err() {
96
+ break;
97
+ }
98
+ }
99
+ }
100
+
101
+ /// Starts no task until `resume`, and waits up to `bound` for the
102
+ /// running ones to end. Returns whether none is running.
103
+ pub fn pause(&self, bound: Duration) -> bool {
104
+ self.queue.pause(bound)
105
+ }
106
+
107
+ pub fn resume(&self) {
108
+ self.queue.resume();
109
+ }
110
+ }
111
+
112
+ /// Taken by every test that fills or times the process's pool, so tests
113
+ /// sharing a process take turns with it.
114
+ #[cfg(test)]
115
+ pub static TESTS: std::sync::Mutex<()> = std::sync::Mutex::new(());
116
+
117
+ #[cfg(test)]
118
+ mod tests {
119
+ use std::sync::mpsc;
120
+ use std::sync::{Arc, Barrier};
121
+ use std::time::Duration;
122
+
123
+ use super::Workers;
124
+ use crate::sys::testing::worker_task;
125
+
126
+ // A pause waits for the task running, holds a task posted meanwhile, and
127
+ // the resume lets it run.
128
+ #[test]
129
+ fn a_pause_waits_for_the_running_task_and_holds_new_ones_until_resumed() {
130
+ crate::process::ensure_initialized();
131
+ let workers: &'static Workers = Box::leak(Box::default());
132
+ let started = Arc::new(Barrier::new(2));
133
+ let release = Arc::new(Barrier::new(2));
134
+ let (ran, order) = mpsc::channel();
135
+ {
136
+ let (started, release, ran) = (Arc::clone(&started), Arc::clone(&release), ran.clone());
137
+ workers.post(
138
+ worker_task(Box::new(move || {
139
+ started.wait();
140
+ release.wait();
141
+ std::thread::sleep(Duration::from_millis(20));
142
+ ran.send("running").expect("receiver");
143
+ })),
144
+ Duration::ZERO,
145
+ );
146
+ }
147
+ started.wait();
148
+ let pausing = std::thread::spawn(|| workers.pause(Duration::from_secs(10)));
149
+ std::thread::sleep(Duration::from_millis(30));
150
+ assert!(!pausing.is_finished());
151
+ workers.post(
152
+ worker_task(Box::new(move || ran.send("posted").expect("receiver"))),
153
+ Duration::ZERO,
154
+ );
155
+ release.wait();
156
+ assert!(pausing.join().expect("pause"));
157
+ assert_eq!(order.try_recv(), Ok("running"));
158
+ assert_eq!(
159
+ order.recv_timeout(Duration::from_millis(50)),
160
+ Err(mpsc::RecvTimeoutError::Timeout)
161
+ );
162
+ workers.resume();
163
+ assert_eq!(order.recv_timeout(Duration::from_secs(10)), Ok("posted"));
164
+ }
165
+ }
@@ -0,0 +1,69 @@
1
+ pub mod code_cache;
2
+ pub mod fork;
3
+ pub mod platform;
4
+ pub(crate) mod tasks;
5
+ pub(crate) mod watchdog;
6
+ pub(crate) mod workers;
7
+
8
+ use std::sync::atomic::{AtomicU64, Ordering};
9
+ use std::sync::{Once, OnceLock};
10
+
11
+ static INIT: Once = Once::new();
12
+ // The platform points into it for the life of the process.
13
+ static PLATFORM_VTABLE: OnceLock<Box<crate::sys::PlatformVtable>> = OnceLock::new();
14
+ static NEXT_THREAD: AtomicU64 = AtomicU64::new(1);
15
+
16
+ thread_local! {
17
+ static THREAD: u64 = NEXT_THREAD.fetch_add(1, Ordering::Relaxed);
18
+ }
19
+
20
+ /// Names the calling thread for the life of the process; never 0.
21
+ #[must_use]
22
+ pub fn thread_token() -> u64 {
23
+ THREAD.with(|token| *token)
24
+ }
25
+
26
+ pub fn ensure_initialized() {
27
+ INIT.call_once(|| {
28
+ let _ = v8::icu::set_common_data_78(deno_core_icudata::ICU_DATA);
29
+ for flag in platform::freeze() {
30
+ v8::V8::set_flags_from_string(&flag);
31
+ }
32
+ workers::configure();
33
+ // Unprotected: on PKU-capable CPUs the protected platform requires every
34
+ // V8 thread to descend from the initializing thread, which Ruby threads
35
+ // and per-context runners do not.
36
+ // Worker tasks go to `workers`, whose threads each process starts for
37
+ // itself, as threads do not survive a fork. V8's own pool keeps the one
38
+ // thread rusty_v8 cannot go below, idle for good.
39
+ let mut platform = v8::new_custom_platform(1, false, true, tasks::Foreground);
40
+ let vtable = PLATFORM_VTABLE.get_or_init(|| crate::sys::worker_vtable(&platform));
41
+ crate::sys::use_vtable(&mut platform, vtable);
42
+ v8::V8::initialize_platform(platform.make_shared());
43
+ v8::V8::initialize();
44
+ crate::sys::register_fork_handlers();
45
+ });
46
+ }
47
+
48
+ /// Whether evals in the shapes `ExecJS` and react-rails send once per call run a
49
+ /// template compiled once per context: `MINI_RACER_RS_FAST_CALLS=off` turns
50
+ /// that off. First read on a Ruby thread, by `ContextHandle::spawn`, where
51
+ /// nothing in Ruby can be changing the environment at the same time.
52
+ pub(crate) fn fast_calls() -> bool {
53
+ static ENABLED: OnceLock<bool> = OnceLock::new();
54
+ *ENABLED.get_or_init(|| {
55
+ std::env::var_os("MINI_RACER_RS_FAST_CALLS").is_none_or(|value| value != "off")
56
+ })
57
+ }
58
+
59
+ /// Tasks V8 posted for an isolate no runner had registered, dropped unrun.
60
+ /// Snapshot builds post some; a context's isolate should never get one.
61
+ #[must_use]
62
+ pub fn unserved_tasks() -> u64 {
63
+ tasks::unserved()
64
+ }
65
+
66
+ #[must_use]
67
+ pub fn v8_version() -> &'static str {
68
+ v8::V8::get_version()
69
+ }