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,485 @@
|
|
|
1
|
+
use std::cell::Cell;
|
|
2
|
+
use std::io::Write;
|
|
3
|
+
use std::sync::OnceLock;
|
|
4
|
+
|
|
5
|
+
use magnus::encoding::{EncodingCapable, RbEncoding};
|
|
6
|
+
use magnus::gc::Marker;
|
|
7
|
+
use magnus::r_hash::ForEach;
|
|
8
|
+
use magnus::scan_args::scan_args;
|
|
9
|
+
use magnus::value::{LazyId, ReprValue};
|
|
10
|
+
use magnus::{
|
|
11
|
+
Class, DataTypeFunctions, Error, Fixnum, Module, RArray, RHash, RModule, RString, Ruby, Symbol,
|
|
12
|
+
TypedData, Value,
|
|
13
|
+
};
|
|
14
|
+
use mini_racer_engine::{ContextConfig, ContextHandle, EngineError, JsText};
|
|
15
|
+
|
|
16
|
+
use crate::encode;
|
|
17
|
+
use crate::gc_root::OwnedRoot;
|
|
18
|
+
use crate::host::{Callbacks, RubyHost, Waiter};
|
|
19
|
+
use crate::options::{self, OptionError, OptionValue};
|
|
20
|
+
use crate::{decode, errors, export, rb_raw, snapshot};
|
|
21
|
+
|
|
22
|
+
// What Ruby's GC is told a live context holds outside its heap: roughly an
|
|
23
|
+
// isolate and its runner thread.
|
|
24
|
+
const ISOLATE_BYTES: usize = 2 << 20;
|
|
25
|
+
const POLL: std::time::Duration = std::time::Duration::from_millis(1);
|
|
26
|
+
// mini_racer warns of every fiftieth level of js->ruby->js nesting.
|
|
27
|
+
const DEEP_NESTING: u32 = 50;
|
|
28
|
+
|
|
29
|
+
static OWNED: LazyId = LazyId::new("owned?");
|
|
30
|
+
|
|
31
|
+
#[derive(Default, TypedData)]
|
|
32
|
+
#[magnus(
|
|
33
|
+
class = "MiniRacer::Context",
|
|
34
|
+
name = "mini_racer/context",
|
|
35
|
+
free_immediately,
|
|
36
|
+
size,
|
|
37
|
+
mark
|
|
38
|
+
)]
|
|
39
|
+
pub struct Context {
|
|
40
|
+
handle: OnceLock<ContextHandle>,
|
|
41
|
+
// One dispatch at a time per context, as mini_racer's per-context mutex;
|
|
42
|
+
// a Ruby Mutex so a waiting thread or fiber stays interruptible.
|
|
43
|
+
lock: OwnedRoot<magnus::Mutex>,
|
|
44
|
+
// What each attached name calls, indexed by the id V8 sends back.
|
|
45
|
+
callables: OwnedRoot<RArray>,
|
|
46
|
+
raised: OwnedRoot<Value>,
|
|
47
|
+
// The IO a fiber scheduler waits on for this context's runner.
|
|
48
|
+
waiter: Waiter,
|
|
49
|
+
accounted: Cell<bool>,
|
|
50
|
+
// Set before the GVL is released to wait for the runner, so a second
|
|
51
|
+
// initialize cannot start while the first waits.
|
|
52
|
+
initializing: Cell<bool>,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
impl DataTypeFunctions for Context {
|
|
56
|
+
fn mark(&self, marker: &Marker) {
|
|
57
|
+
self.lock.mark(marker);
|
|
58
|
+
self.callables.mark(marker);
|
|
59
|
+
self.raised.mark(marker);
|
|
60
|
+
self.waiter.mark(marker);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
impl Drop for Context {
|
|
65
|
+
fn drop(&mut self) {
|
|
66
|
+
if self.accounted.get() {
|
|
67
|
+
rb_raw::release_memory(ISOLATE_BYTES);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pub fn init(ruby: &Ruby, racer: RModule) -> Result<(), Error> {
|
|
73
|
+
let class = racer.define_class("Context", ruby.class_object())?;
|
|
74
|
+
class.define_alloc_func::<Context>();
|
|
75
|
+
class.define_method("initialize", export!(method Context::initialize, -1))?;
|
|
76
|
+
class.define_method("eval", export!(method Context::eval, -1))?;
|
|
77
|
+
class.define_method("eval_await", export!(method Context::eval_await, -1))?;
|
|
78
|
+
class.define_method("call", export!(method Context::call, -1))?;
|
|
79
|
+
class.define_method("call_await", export!(method Context::call_await, -1))?;
|
|
80
|
+
class.define_method(
|
|
81
|
+
"perform_microtask_checkpoint",
|
|
82
|
+
export!(method Context::perform_microtask_checkpoint, 0),
|
|
83
|
+
)?;
|
|
84
|
+
class.define_method(
|
|
85
|
+
"pump_message_loop",
|
|
86
|
+
export!(method Context::pump_message_loop, 0),
|
|
87
|
+
)?;
|
|
88
|
+
class.define_method("heap_stats", export!(method Context::heap_stats, 0))?;
|
|
89
|
+
class.define_method("heap_snapshot", export!(method Context::heap_snapshot, 0))?;
|
|
90
|
+
class.define_method("attach", export!(method Context::attach, 2))?;
|
|
91
|
+
class.define_method(
|
|
92
|
+
"low_memory_notification",
|
|
93
|
+
export!(method Context::low_memory_notification, 0),
|
|
94
|
+
)?;
|
|
95
|
+
class.define_method("stop", export!(method Context::stop, 0))?;
|
|
96
|
+
class.define_method("dispose", export!(method Context::dispose, 0))?;
|
|
97
|
+
Ok(())
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
struct Locked(magnus::Mutex);
|
|
101
|
+
|
|
102
|
+
// Mutex#lock's refusal to wait inside a signal trap, told apart by its message
|
|
103
|
+
// from a ThreadError another thread raised into this one.
|
|
104
|
+
fn refused_in_trap(ruby: &Ruby, error: &Error) -> bool {
|
|
105
|
+
error.is_kind_of(ruby.exception_thread_error())
|
|
106
|
+
&& error
|
|
107
|
+
.value()
|
|
108
|
+
.and_then(|exception| exception.funcall::<_, _, String>("message", ()).ok())
|
|
109
|
+
.is_some_and(|message| message == "can't be called from trap context")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
impl Drop for Locked {
|
|
113
|
+
fn drop(&mut self) {
|
|
114
|
+
let _ = self.0.unlock();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// mini_racer sends ISO-8859-1 and UTF-16LE strings in their own encoding and
|
|
119
|
+
// every other string's bytes as UTF-8.
|
|
120
|
+
pub fn js_text(ruby: &Ruby, string: RString) -> Result<JsText, Error> {
|
|
121
|
+
let bytes = rb_raw::string_bytes(ruby, string);
|
|
122
|
+
Ok(match RbEncoding::from(string.enc_get()).name() {
|
|
123
|
+
"ISO-8859-1" => JsText::Latin1(bytes),
|
|
124
|
+
"UTF-16LE" => {
|
|
125
|
+
let (units, rest) = bytes.as_chunks::<2>();
|
|
126
|
+
if !rest.is_empty() {
|
|
127
|
+
return Err(errors::undeserializable(ruby));
|
|
128
|
+
}
|
|
129
|
+
JsText::Utf16(units.iter().map(|unit| u16::from_le_bytes(*unit)).collect())
|
|
130
|
+
}
|
|
131
|
+
_ => JsText::Utf8(bytes),
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// mini_racer looks for DateTime at each Context.new until it exists.
|
|
136
|
+
fn resolve_date_time(ruby: &Ruby) -> Result<(), Error> {
|
|
137
|
+
if encode::DATE_TIME.get(ruby).is_none()
|
|
138
|
+
&& ruby
|
|
139
|
+
.class_object()
|
|
140
|
+
.funcall::<_, _, bool>("const_defined?", ("DateTime",))?
|
|
141
|
+
{
|
|
142
|
+
encode::DATE_TIME.set(ruby, ruby.class_object().const_get::<_, Value>("DateTime")?);
|
|
143
|
+
}
|
|
144
|
+
Ok(())
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
fn option_value(ruby: &Ruby, value: Value) -> OptionValue {
|
|
148
|
+
let other = || {
|
|
149
|
+
if value.is_nil() {
|
|
150
|
+
OptionValue::Nil
|
|
151
|
+
} else if value.as_value().equal(ruby.qfalse()).unwrap_or(false) {
|
|
152
|
+
OptionValue::False
|
|
153
|
+
} else {
|
|
154
|
+
OptionValue::Other(rb_raw::type_name(ruby, value))
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
Fixnum::from_value(value).map_or_else(other, |n| OptionValue::Fixnum(n.to_i64()))
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// mini_racer's typed data check names a typed object of the wrong kind by
|
|
161
|
+
// its struct name.
|
|
162
|
+
fn snapshot_value(ruby: &Ruby, value: Value) -> OptionValue {
|
|
163
|
+
if let Some(shared) = snapshot::shared_of(ruby, value) {
|
|
164
|
+
return OptionValue::Snapshot(shared);
|
|
165
|
+
}
|
|
166
|
+
match (
|
|
167
|
+
option_value(ruby, value),
|
|
168
|
+
rb_raw::typed_data_name(ruby, value),
|
|
169
|
+
) {
|
|
170
|
+
(OptionValue::Other(_), Some(name)) => OptionValue::Other(name),
|
|
171
|
+
(other, _) => other,
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
fn config(ruby: &Ruby, keywords: RHash) -> Result<ContextConfig, Error> {
|
|
176
|
+
let mut pairs = Vec::new();
|
|
177
|
+
keywords.foreach(|key: Symbol, value: Value| {
|
|
178
|
+
let key = key.name()?.into_owned();
|
|
179
|
+
let value = if key == "snapshot" {
|
|
180
|
+
snapshot_value(ruby, value)
|
|
181
|
+
} else {
|
|
182
|
+
option_value(ruby, value)
|
|
183
|
+
};
|
|
184
|
+
pairs.push((key, value));
|
|
185
|
+
Ok(ForEach::Continue)
|
|
186
|
+
})?;
|
|
187
|
+
options::configure(pairs.into_iter()).map_err(|error| match error {
|
|
188
|
+
OptionError::Type(_) | OptionError::SnapshotType(_) => {
|
|
189
|
+
errors::raised(ruby, ruby.exception_type_error(), error.to_string())
|
|
190
|
+
}
|
|
191
|
+
OptionError::Range(_) => {
|
|
192
|
+
errors::raised(ruby, ruby.exception_arg_error(), error.to_string())
|
|
193
|
+
}
|
|
194
|
+
OptionError::Keyword(_) => errors::runtime(ruby, error.to_string()),
|
|
195
|
+
OptionError::InvalidSnapshot(invalid) => errors::engine(ruby, invalid.into()),
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
impl Context {
|
|
200
|
+
fn handle(&self, ruby: &Ruby) -> Result<&ContextHandle, Error> {
|
|
201
|
+
self.handle
|
|
202
|
+
.get()
|
|
203
|
+
.ok_or_else(|| errors::internal(ruby, "context is not initialized"))
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
fn mutex(&self, ruby: &Ruby) -> Result<magnus::Mutex, Error> {
|
|
207
|
+
self.lock
|
|
208
|
+
.get(ruby)
|
|
209
|
+
.ok_or_else(|| errors::internal(ruby, "context is not initialized"))
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fn callables(&self, ruby: &Ruby) -> Result<RArray, Error> {
|
|
213
|
+
self.callables
|
|
214
|
+
.get(ruby)
|
|
215
|
+
.ok_or_else(|| errors::internal(ruby, "context is not initialized"))
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// trylock first: an uncontended call then works from a signal trap, where
|
|
219
|
+
// Mutex#lock raises. A callback's own calls into this context run inside
|
|
220
|
+
// the dispatch that made the callback, which already holds the lock.
|
|
221
|
+
fn acquire(&self, ruby: &Ruby) -> Result<Option<Locked>, Error> {
|
|
222
|
+
let mutex = self.mutex(ruby)?;
|
|
223
|
+
if mutex.trylock() {
|
|
224
|
+
return Ok(Some(Locked(mutex)));
|
|
225
|
+
}
|
|
226
|
+
if self.handle(ruby)?.in_callback() && mutex.funcall::<_, _, bool>(*OWNED, ())? {
|
|
227
|
+
return Ok(None);
|
|
228
|
+
}
|
|
229
|
+
rb_raw::mutex_lock(ruby, mutex)?;
|
|
230
|
+
Ok(Some(Locked(mutex)))
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
fn initialize(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<(), Error> {
|
|
234
|
+
resolve_date_time(ruby)?;
|
|
235
|
+
if rb_self.handle.get().is_some() || rb_self.initializing.get() {
|
|
236
|
+
return Err(errors::internal(ruby, "context is already initialized"));
|
|
237
|
+
}
|
|
238
|
+
let args = scan_args::<(), (), (), (), RHash, ()>(args)?;
|
|
239
|
+
let config = config(ruby, args.keywords)?;
|
|
240
|
+
rb_self.initializing.set(true);
|
|
241
|
+
let mut host = RubyHost::new(ruby).waiting_on(&rb_self.waiter);
|
|
242
|
+
let spawned = ContextHandle::spawn(config, &mut host);
|
|
243
|
+
let handle = host.finish(spawned);
|
|
244
|
+
rb_self.initializing.set(false);
|
|
245
|
+
let handle = handle?;
|
|
246
|
+
rb_self.lock.set(ruby, ruby.mutex_new());
|
|
247
|
+
rb_self.callables.set(ruby, ruby.ary_new());
|
|
248
|
+
rb_self
|
|
249
|
+
.handle
|
|
250
|
+
.set(handle)
|
|
251
|
+
.map_err(|_| errors::internal(ruby, "context is already initialized"))?;
|
|
252
|
+
rb_raw::adjust_memory(ruby, ISOLATE_BYTES.cast_signed());
|
|
253
|
+
rb_self.accounted.set(true);
|
|
254
|
+
Ok(())
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
fn eval(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Value, Error> {
|
|
258
|
+
Self::evaluate(ruby, rb_self, args, false)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
fn eval_await(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Value, Error> {
|
|
262
|
+
Self::evaluate(ruby, rb_self, args, true)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fn evaluate(
|
|
266
|
+
ruby: &Ruby,
|
|
267
|
+
rb_self: &Self,
|
|
268
|
+
args: &[Value],
|
|
269
|
+
awaited: bool,
|
|
270
|
+
) -> Result<Value, Error> {
|
|
271
|
+
let handle = rb_self.handle(ruby)?;
|
|
272
|
+
let args = scan_args::<(Value,), (), (), (), RHash, ()>(args)?;
|
|
273
|
+
let source = rb_raw::expect_string(ruby, args.required.0)?;
|
|
274
|
+
let filename = match args.keywords.get(ruby.to_symbol("filename")) {
|
|
275
|
+
Some(filename) if !filename.is_nil() => {
|
|
276
|
+
js_text(ruby, rb_raw::expect_string(ruby, filename)?)?
|
|
277
|
+
}
|
|
278
|
+
_ => JsText::from("<eval>"),
|
|
279
|
+
};
|
|
280
|
+
let source = js_text(ruby, source)?;
|
|
281
|
+
let bytes = rb_self.dispatch(ruby, |host| {
|
|
282
|
+
if awaited {
|
|
283
|
+
handle.eval_await(filename, source, host)
|
|
284
|
+
} else {
|
|
285
|
+
handle.eval(filename, source, host)
|
|
286
|
+
}
|
|
287
|
+
})?;
|
|
288
|
+
decode::value(ruby, &bytes)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
fn call(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Value, Error> {
|
|
292
|
+
Self::invoke(ruby, rb_self, args, false)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
fn call_await(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Value, Error> {
|
|
296
|
+
Self::invoke(ruby, rb_self, args, true)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
fn invoke(ruby: &Ruby, rb_self: &Self, args: &[Value], awaited: bool) -> Result<Value, Error> {
|
|
300
|
+
let handle = rb_self.handle(ruby)?;
|
|
301
|
+
let args = scan_args::<(Value,), (), RArray, (), (), ()>(args)?;
|
|
302
|
+
let name = rb_raw::expect_string(ruby, args.required.0)?;
|
|
303
|
+
let request = args.splat;
|
|
304
|
+
request.unshift(name)?;
|
|
305
|
+
let request = encode::encode(ruby, request.as_value(), |message| {
|
|
306
|
+
errors::runtime(ruby, [b"Context.call: ".as_slice(), &message].concat())
|
|
307
|
+
})?;
|
|
308
|
+
let bytes = rb_self.dispatch(ruby, |host| {
|
|
309
|
+
if awaited {
|
|
310
|
+
handle.call_await(request, host)
|
|
311
|
+
} else {
|
|
312
|
+
handle.call(request, host)
|
|
313
|
+
}
|
|
314
|
+
})?;
|
|
315
|
+
decode::value(ruby, &bytes)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
fn perform_microtask_checkpoint(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
|
|
319
|
+
let handle = rb_self.handle(ruby)?;
|
|
320
|
+
rb_self.dispatch(ruby, |host| handle.perform_microtask_checkpoint(host))
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
fn pump_message_loop(ruby: &Ruby, rb_self: &Self) -> Result<bool, Error> {
|
|
324
|
+
let handle = rb_self.handle(ruby)?;
|
|
325
|
+
rb_self.dispatch(ruby, |host| handle.pump_message_loop(host))
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// mini_racer turns each statistic's name into a symbol.
|
|
329
|
+
fn heap_stats(ruby: &Ruby, rb_self: &Self) -> Result<RHash, Error> {
|
|
330
|
+
let handle = rb_self.handle(ruby)?;
|
|
331
|
+
let bytes = rb_self.dispatch(ruby, |host| handle.heap_stats(host))?;
|
|
332
|
+
let named = RHash::from_value(decode::value(ruby, &bytes)?)
|
|
333
|
+
.ok_or_else(|| errors::internal(ruby, "heap statistics are not an object"))?;
|
|
334
|
+
let stats = ruby.hash_new();
|
|
335
|
+
named.foreach(|name: RString, value: Value| {
|
|
336
|
+
stats.aset(ruby.to_symbol(name.to_string()?), value)?;
|
|
337
|
+
Ok(ForEach::Continue)
|
|
338
|
+
})?;
|
|
339
|
+
Ok(stats)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// mini_racer sends this one without its result decoding, which also
|
|
343
|
+
// leaves the exception a callback raised for the enclosing dispatch.
|
|
344
|
+
fn heap_snapshot(ruby: &Ruby, rb_self: &Self) -> Result<RString, Error> {
|
|
345
|
+
let handle = rb_self.handle(ruby)?;
|
|
346
|
+
let _locked = rb_self.acquire(ruby)?;
|
|
347
|
+
rb_self.warn_if_deep(ruby)?;
|
|
348
|
+
let mut host = RubyHost::new(ruby).waiting_on(&rb_self.waiter);
|
|
349
|
+
let result = handle.heap_snapshot(&mut host);
|
|
350
|
+
let json = host.finish(result)?;
|
|
351
|
+
Ok(ruby.enc_str_new(json, ruby.utf8_encoding()))
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// The lock is held from choosing the id to sending it, so two threads
|
|
355
|
+
// attaching at once cannot be given the same one.
|
|
356
|
+
fn attach(ruby: &Ruby, rb_self: &Self, name: Value, callable: Value) -> Result<Value, Error> {
|
|
357
|
+
let handle = rb_self.handle(ruby)?;
|
|
358
|
+
let callables = rb_self.callables(ruby)?;
|
|
359
|
+
let name = rb_raw::expect_string(ruby, name)?;
|
|
360
|
+
if handle.is_disposed() {
|
|
361
|
+
return Err(errors::engine(ruby, EngineError::Disposed));
|
|
362
|
+
}
|
|
363
|
+
let _locked = rb_self.acquire(ruby)?;
|
|
364
|
+
let id = i32::try_from(callables.len())
|
|
365
|
+
.map_err(|_| errors::runtime(ruby, "too many callbacks"))?;
|
|
366
|
+
let request = ruby.ary_new_capa(2);
|
|
367
|
+
request.push(name)?;
|
|
368
|
+
request.push(id)?;
|
|
369
|
+
let request = encode::encode(ruby, request.as_value(), |message| {
|
|
370
|
+
errors::runtime(ruby, message)
|
|
371
|
+
})?;
|
|
372
|
+
callables.push(callable)?;
|
|
373
|
+
rb_self.run_locked(ruby, |host| handle.attach(request, host))?;
|
|
374
|
+
Ok(ruby.qnil().as_value())
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Returns the result undecoded: decoding runs Ruby code, so it happens
|
|
378
|
+
// after the lock is released, as in mini_racer.
|
|
379
|
+
fn dispatch<T>(
|
|
380
|
+
&self,
|
|
381
|
+
ruby: &Ruby,
|
|
382
|
+
run: impl FnOnce(&mut RubyHost<'_>) -> Result<T, EngineError>,
|
|
383
|
+
) -> Result<T, Error> {
|
|
384
|
+
let _locked = self.acquire(ruby)?;
|
|
385
|
+
self.run_locked(ruby, run)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
fn run_locked<T>(
|
|
389
|
+
&self,
|
|
390
|
+
ruby: &Ruby,
|
|
391
|
+
run: impl FnOnce(&mut RubyHost<'_>) -> Result<T, EngineError>,
|
|
392
|
+
) -> Result<T, Error> {
|
|
393
|
+
self.warn_if_deep(ruby)?;
|
|
394
|
+
let callbacks = Callbacks {
|
|
395
|
+
callables: self.callables(ruby)?,
|
|
396
|
+
raised: &self.raised,
|
|
397
|
+
};
|
|
398
|
+
let mut host = RubyHost::with_callbacks(ruby, callbacks).waiting_on(&self.waiter);
|
|
399
|
+
let result = run(&mut host);
|
|
400
|
+
host.finish(result)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
fn warn_if_deep(&self, ruby: &Ruby) -> Result<(), Error> {
|
|
404
|
+
let depth = self.handle(ruby)?.depth();
|
|
405
|
+
if depth > 0 && depth % DEEP_NESTING == 0 {
|
|
406
|
+
let _ = writeln!(
|
|
407
|
+
std::io::stderr(),
|
|
408
|
+
"mini_racer: deep js->ruby->js recursion, depth={depth}"
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
Ok(())
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// mini_racer sends this one without its result decoding, which also
|
|
415
|
+
// leaves the exception a callback raised for the enclosing dispatch.
|
|
416
|
+
fn low_memory_notification(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
|
|
417
|
+
let handle = rb_self.handle(ruby)?;
|
|
418
|
+
let _locked = rb_self.acquire(ruby)?;
|
|
419
|
+
rb_self.warn_if_deep(ruby)?;
|
|
420
|
+
let mut host = RubyHost::new(ruby).waiting_on(&rb_self.waiter);
|
|
421
|
+
let result = handle.low_memory_notification(&mut host);
|
|
422
|
+
host.finish(result)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
fn stop(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
|
|
426
|
+
rb_self
|
|
427
|
+
.handle(ruby)?
|
|
428
|
+
.stop()
|
|
429
|
+
.map_err(|e| errors::engine(ruby, e))
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Terminates a dispatch running on another thread first, so waiting for the
|
|
433
|
+
// context lock then takes only as long as that dispatch needs to unwind.
|
|
434
|
+
// From the thread that holds the lock (a signal trap during eval) it is
|
|
435
|
+
// refused, as mini_racer refuses it, and the running eval is left alone.
|
|
436
|
+
// Once begun, a dispose finishes: an interrupt while it waits for the lock
|
|
437
|
+
// is kept and raised after the runner is gone, as mini_racer's dispose
|
|
438
|
+
// ignores interrupts until it is done.
|
|
439
|
+
fn dispose(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
|
|
440
|
+
let handle = rb_self.handle(ruby)?;
|
|
441
|
+
let mutex = rb_self.mutex(ruby)?;
|
|
442
|
+
// From inside a callback, on any fiber of its thread, the lock can
|
|
443
|
+
// never be had, so mini_racer's busy error comes before anything stops.
|
|
444
|
+
if handle.in_callback() || mutex.funcall::<_, _, bool>(*OWNED, ())? {
|
|
445
|
+
return Err(errors::engine(ruby, EngineError::DisposeBusy));
|
|
446
|
+
}
|
|
447
|
+
handle.begin_dispose();
|
|
448
|
+
let mut interrupted = None;
|
|
449
|
+
let mut polling = false;
|
|
450
|
+
while !mutex.trylock() {
|
|
451
|
+
if polling {
|
|
452
|
+
// Released between attempts, so the thread holding the lock
|
|
453
|
+
// can finish its terminated eval and unlock.
|
|
454
|
+
if rb_raw::nogvl(ruby, || std::thread::sleep(POLL), &|| {}).is_none()
|
|
455
|
+
&& let Err(error) = rb_raw::check_ints(ruby)
|
|
456
|
+
{
|
|
457
|
+
interrupted.get_or_insert(error);
|
|
458
|
+
}
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
match rb_raw::mutex_lock(ruby, mutex) {
|
|
462
|
+
Ok(()) => break,
|
|
463
|
+
Err(error) if refused_in_trap(ruby, &error) => polling = true,
|
|
464
|
+
// Waiting on a lock another fiber of this thread holds.
|
|
465
|
+
Err(error) if error.is_kind_of(ruby.exception_thread_error()) => return Err(error),
|
|
466
|
+
Err(error) => {
|
|
467
|
+
interrupted.get_or_insert(error);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
let _locked = Locked(mutex);
|
|
472
|
+
let mut host = RubyHost::new(ruby).waiting_on(&rb_self.waiter);
|
|
473
|
+
let result = handle.dispose(&mut host);
|
|
474
|
+
let disposed = host.finish(result);
|
|
475
|
+
if disposed.is_ok() {
|
|
476
|
+
if rb_self.accounted.replace(false) {
|
|
477
|
+
rb_raw::adjust_memory(ruby, -ISOLATE_BYTES.cast_signed());
|
|
478
|
+
}
|
|
479
|
+
if let Some(io) = rb_self.waiter.take(ruby) {
|
|
480
|
+
let _: Value = io.funcall("close", ())?;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
interrupted.map_or(disposed, Err)
|
|
484
|
+
}
|
|
485
|
+
}
|