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,239 @@
|
|
|
1
|
+
use std::sync::{Arc, Mutex, OnceLock};
|
|
2
|
+
use std::time::{Duration, Instant};
|
|
3
|
+
|
|
4
|
+
use mini_racer_sync::{Channel, DispatchId, Protocol, RunnerPhase, TermCell, Terminator, lock};
|
|
5
|
+
|
|
6
|
+
use crate::notify::NotifyFd;
|
|
7
|
+
use crate::process::tasks::Tasks;
|
|
8
|
+
use crate::{EngineError, SnapshotBlob};
|
|
9
|
+
|
|
10
|
+
pub struct Wire;
|
|
11
|
+
|
|
12
|
+
impl Protocol for Wire {
|
|
13
|
+
type Request = Request;
|
|
14
|
+
type Response = Result<Vec<u8>, EngineError>;
|
|
15
|
+
type Callback = Vec<u8>;
|
|
16
|
+
type Reply = CallbackReply;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pub enum CallbackReply {
|
|
20
|
+
Value(Vec<u8>),
|
|
21
|
+
/// The callback raised: V8 throws an `Error` with this message, and an
|
|
22
|
+
/// outermost or nested dispatch it escapes ends with
|
|
23
|
+
/// `JsErrorKind::HostException`.
|
|
24
|
+
Raised(JsText),
|
|
25
|
+
/// The caller abandoned the dispatch: V8 throws `Error: terminated` and,
|
|
26
|
+
/// unless the script already has its verdict, terminates it.
|
|
27
|
+
Abort,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
31
|
+
pub enum Depth {
|
|
32
|
+
Outermost(DispatchId),
|
|
33
|
+
Nested,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Text in the encoding Ruby holds it in. V8 reads each form directly, so a
|
|
37
|
+
/// lone surrogate in UTF-16 and invalid UTF-8 reach it as `mini_racer` sends
|
|
38
|
+
/// them.
|
|
39
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
40
|
+
pub enum JsText {
|
|
41
|
+
Utf8(Vec<u8>),
|
|
42
|
+
Latin1(Vec<u8>),
|
|
43
|
+
Utf16(Vec<u16>),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
impl JsText {
|
|
47
|
+
/// Its length in code units of its own encoding.
|
|
48
|
+
#[must_use]
|
|
49
|
+
pub const fn units(&self) -> usize {
|
|
50
|
+
match self {
|
|
51
|
+
Self::Utf8(bytes) | Self::Latin1(bytes) => bytes.len(),
|
|
52
|
+
Self::Utf16(units) => units.len(),
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
impl From<String> for JsText {
|
|
58
|
+
fn from(text: String) -> Self {
|
|
59
|
+
Self::Utf8(text.into_bytes())
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
impl From<&str> for JsText {
|
|
64
|
+
fn from(text: &str) -> Self {
|
|
65
|
+
Self::Utf8(text.as_bytes().to_vec())
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
pub enum Request {
|
|
70
|
+
Init {
|
|
71
|
+
depth: Depth,
|
|
72
|
+
},
|
|
73
|
+
/// `awaited`: a promise result is awaited, as `eval_await` does.
|
|
74
|
+
Eval {
|
|
75
|
+
filename: JsText,
|
|
76
|
+
source: JsText,
|
|
77
|
+
depth: Depth,
|
|
78
|
+
awaited: bool,
|
|
79
|
+
},
|
|
80
|
+
Call {
|
|
81
|
+
request: Vec<u8>,
|
|
82
|
+
depth: Depth,
|
|
83
|
+
awaited: bool,
|
|
84
|
+
},
|
|
85
|
+
Attach {
|
|
86
|
+
request: Vec<u8>,
|
|
87
|
+
depth: Depth,
|
|
88
|
+
},
|
|
89
|
+
LowMemoryNotification {
|
|
90
|
+
depth: Depth,
|
|
91
|
+
},
|
|
92
|
+
MicrotaskCheckpoint {
|
|
93
|
+
depth: Depth,
|
|
94
|
+
},
|
|
95
|
+
/// The response is a wire object of numbers, under `mini_racer`'s names.
|
|
96
|
+
HeapStats {
|
|
97
|
+
depth: Depth,
|
|
98
|
+
},
|
|
99
|
+
/// The response is the JSON V8 writes, not wire bytes.
|
|
100
|
+
HeapSnapshot {
|
|
101
|
+
depth: Depth,
|
|
102
|
+
},
|
|
103
|
+
/// Runs one due task; the response is one byte, 1 if a task ran.
|
|
104
|
+
PumpMessageLoop {
|
|
105
|
+
depth: Depth,
|
|
106
|
+
},
|
|
107
|
+
// Built on the runner of a context made for it, as mini_racer builds one
|
|
108
|
+
// on a throwaway context: from `code` alone, or by warming `base` up.
|
|
109
|
+
Snapshot {
|
|
110
|
+
code: JsText,
|
|
111
|
+
base: Option<SnapshotBlob>,
|
|
112
|
+
depth: Depth,
|
|
113
|
+
},
|
|
114
|
+
Dispose,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
pub struct IsolateTerminator {
|
|
118
|
+
handle: v8::IsolateHandle,
|
|
119
|
+
tasks: Arc<Tasks>,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
impl IsolateTerminator {
|
|
123
|
+
pub fn new(handle: v8::IsolateHandle, tasks: &Arc<Tasks>) -> Self {
|
|
124
|
+
Self {
|
|
125
|
+
handle,
|
|
126
|
+
tasks: Arc::clone(tasks),
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
impl Terminator for IsolateTerminator {
|
|
132
|
+
// A runner awaiting a promise sleeps until a task is due; the ring wakes
|
|
133
|
+
// it to find the termination.
|
|
134
|
+
fn terminate(&self) {
|
|
135
|
+
self.handle.terminate_execution();
|
|
136
|
+
self.tasks.ring();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fn cancel(&self) {
|
|
140
|
+
self.handle.cancel_terminate_execution();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
|
145
|
+
pub struct ContextConfig {
|
|
146
|
+
pub timeout: Option<Duration>,
|
|
147
|
+
/// The blob the isolate starts from, shared with every other context made
|
|
148
|
+
/// from it. V8 reads it for as long as the isolate lives, so it stays here,
|
|
149
|
+
/// in the block the runner holds until it has dropped the isolate.
|
|
150
|
+
pub snapshot: Option<SnapshotBlob>,
|
|
151
|
+
/// The used heap size, in bytes, past which a garbage collection ends the
|
|
152
|
+
/// running script out of memory, as `mini_racer`'s `max_memory:` does.
|
|
153
|
+
pub max_memory: Option<u64>,
|
|
154
|
+
/// How long a runner waits idle after a request before it asks V8 for a
|
|
155
|
+
/// full garbage collection, once, as `mini_racer`'s
|
|
156
|
+
/// `ensure_gc_after_idle:` does. Single-threaded runners never do.
|
|
157
|
+
pub gc_after_idle: Option<Duration>,
|
|
158
|
+
/// Has V8 report each exception that escapes a script, as `mini_racer`'s
|
|
159
|
+
/// verbose `TryCatch` does: it prints where it happened to stdout.
|
|
160
|
+
pub verbose_exceptions: bool,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// The context's pending timeout and the earliest deadline the watchdog holds
|
|
164
|
+
/// for it, so the watchdog keeps about one entry per context, not per dispatch.
|
|
165
|
+
#[derive(Default)]
|
|
166
|
+
pub struct Timer {
|
|
167
|
+
pub due: Option<(Instant, DispatchId)>,
|
|
168
|
+
pub queued: Option<Instant>,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Shared by the handle, its runner and the watchdog's weak references.
|
|
172
|
+
pub struct ControlBlock {
|
|
173
|
+
pub channel: Channel<Wire>,
|
|
174
|
+
pub term: TermCell<IsolateTerminator>,
|
|
175
|
+
pub config: ContextConfig,
|
|
176
|
+
pub timer: Mutex<Timer>,
|
|
177
|
+
pub notify: NotifyFd,
|
|
178
|
+
// V8's foreground tasks for this context's isolate, which the runner runs
|
|
179
|
+
// while awaiting a promise or pumping.
|
|
180
|
+
pub tasks: Arc<Tasks>,
|
|
181
|
+
pub phase: RunnerPhase,
|
|
182
|
+
pub isolate: OnceLock<v8::IsolateHandle>,
|
|
183
|
+
// The isolate a snapshot is being built in on this runner, if any.
|
|
184
|
+
pub building: Mutex<Option<v8::IsolateHandle>>,
|
|
185
|
+
// An idle single-threaded runner leaves its isolate here, its only owner
|
|
186
|
+
// then, so a forked child can hand it to a runner of its own.
|
|
187
|
+
pub custody: Mutex<Option<v8::SharedIsolate>>,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
impl Drop for ControlBlock {
|
|
191
|
+
// An isolate is dropped only by a runner (I8). One still here was never
|
|
192
|
+
// taken back, as when its runner thread could not start or panicked.
|
|
193
|
+
fn drop(&mut self) {
|
|
194
|
+
let isolate = lock(&self.custody).take();
|
|
195
|
+
if let Some(isolate) = isolate {
|
|
196
|
+
#[expect(
|
|
197
|
+
clippy::mem_forget,
|
|
198
|
+
reason = "leaked rather than disposed off its runner"
|
|
199
|
+
)]
|
|
200
|
+
std::mem::forget(isolate);
|
|
201
|
+
#[expect(clippy::mem_forget, reason = "the leaked isolate may still read it")]
|
|
202
|
+
std::mem::forget(self.config.snapshot.take());
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
impl ControlBlock {
|
|
208
|
+
/// Asks the runner to park for a pending fork, in JavaScript or in a
|
|
209
|
+
/// snapshot build, wherever V8 next checks for interrupts.
|
|
210
|
+
pub fn interrupt_for_fork(&self) {
|
|
211
|
+
if let Some(handle) = self.isolate.get() {
|
|
212
|
+
crate::sys::interrupt_for_fork(handle);
|
|
213
|
+
}
|
|
214
|
+
let building = lock(&self.building).clone();
|
|
215
|
+
if let Some(handle) = building {
|
|
216
|
+
crate::sys::interrupt_for_fork(&handle);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
pub fn new(config: ContextConfig) -> Self {
|
|
221
|
+
Self::with_tasks(config, Tasks::default())
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/// For a forked child taking over an idle context with its queued tasks.
|
|
225
|
+
pub fn with_tasks(config: ContextConfig, tasks: Tasks) -> Self {
|
|
226
|
+
Self {
|
|
227
|
+
channel: Channel::default(),
|
|
228
|
+
term: TermCell::default(),
|
|
229
|
+
config,
|
|
230
|
+
timer: Mutex::default(),
|
|
231
|
+
notify: NotifyFd::default(),
|
|
232
|
+
tasks: Arc::new(tasks),
|
|
233
|
+
phase: RunnerPhase::default(),
|
|
234
|
+
isolate: OnceLock::new(),
|
|
235
|
+
building: Mutex::new(None),
|
|
236
|
+
custody: Mutex::new(None),
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
pub use mini_racer_sync::TerminationCause;
|
|
2
|
+
|
|
3
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
|
4
|
+
pub enum FlagError {
|
|
5
|
+
#[error("flag contains NUL byte")]
|
|
6
|
+
Nul,
|
|
7
|
+
#[error("flag too long")]
|
|
8
|
+
TooLong,
|
|
9
|
+
#[error("platform already initialized")]
|
|
10
|
+
AlreadyInitialized,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/// Why a startup snapshot's blob is refused before V8 reads it, where V8
|
|
14
|
+
/// itself would abort the process.
|
|
15
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
|
16
|
+
pub enum InvalidSnapshot {
|
|
17
|
+
/// Shorter than its own header, or its sections do not fit in it.
|
|
18
|
+
#[error("invalid snapshot: truncated or corrupt")]
|
|
19
|
+
Corrupt,
|
|
20
|
+
#[error("invalid snapshot: not made by V8 {}", crate::process::v8_version())]
|
|
21
|
+
Version,
|
|
22
|
+
/// Empty, where a warmup needs a snapshot to start from.
|
|
23
|
+
#[error("invalid snapshot: empty")]
|
|
24
|
+
Empty,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
28
|
+
pub enum JsErrorKind {
|
|
29
|
+
Parse,
|
|
30
|
+
Runtime,
|
|
31
|
+
/// V8 could not read the request itself.
|
|
32
|
+
Internal,
|
|
33
|
+
Terminated(TerminationCause),
|
|
34
|
+
/// The exception escaping the dispatch is the one V8 threw for a raising
|
|
35
|
+
/// callback, so the caller raises the Ruby exception it kept instead.
|
|
36
|
+
HostException,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
|
40
|
+
#[error("{message}")]
|
|
41
|
+
pub struct JsError {
|
|
42
|
+
pub kind: JsErrorKind,
|
|
43
|
+
pub message: String,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
impl JsError {
|
|
47
|
+
pub(crate) fn terminated(cause: TerminationCause) -> Self {
|
|
48
|
+
let message = match cause {
|
|
49
|
+
TerminationCause::MaxMemory | TerminationCause::HeapExhausted => "out of memory",
|
|
50
|
+
TerminationCause::Stop | TerminationCause::Timeout | TerminationCause::Cancelled => {
|
|
51
|
+
"terminated"
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
Self {
|
|
55
|
+
kind: JsErrorKind::Terminated(cause),
|
|
56
|
+
message: message.to_owned(),
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#[derive(Debug, thiserror::Error)]
|
|
62
|
+
pub enum EngineError {
|
|
63
|
+
#[error(transparent)]
|
|
64
|
+
Js(#[from] JsError),
|
|
65
|
+
#[error("context disposed")]
|
|
66
|
+
Disposed,
|
|
67
|
+
#[error("single-threaded runner: {}", ebusy())]
|
|
68
|
+
Orphaned,
|
|
69
|
+
#[error("context busy: another thread is using it")]
|
|
70
|
+
Busy,
|
|
71
|
+
#[error("context dispose: {}", ebusy())]
|
|
72
|
+
DisposeBusy,
|
|
73
|
+
/// `mini_racer`'s text for a snapshot that could not be built.
|
|
74
|
+
#[error("{0}")]
|
|
75
|
+
Snapshot(String),
|
|
76
|
+
#[error(transparent)]
|
|
77
|
+
InvalidSnapshot(#[from] InvalidSnapshot),
|
|
78
|
+
#[error("internal error: {0}")]
|
|
79
|
+
Internal(String),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn ebusy() -> String {
|
|
83
|
+
let message = std::io::Error::from_raw_os_error(libc::EBUSY).to_string();
|
|
84
|
+
match message.rsplit_once(" (os error ") {
|
|
85
|
+
Some((text, _)) => text.to_owned(),
|
|
86
|
+
None => message,
|
|
87
|
+
}
|
|
88
|
+
}
|