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,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
+ }