mini_racer 0.17.0.pre6 → 0.17.0.pre7
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 +4 -4
- data/CHANGELOG +5 -0
- data/README.md +7 -38
- data/ext/mini_racer_extension/mini_racer_v8.cc +50 -68
- data/lib/mini_racer/shared.rb +380 -0
- data/lib/mini_racer/truffleruby.rb +36 -40
- data/lib/mini_racer/version.rb +1 -1
- data/lib/mini_racer.rb +2 -4
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d058a397309a0e5354576fa25b299f26e9ca9576b23333063dbcd46fcf9f775
|
4
|
+
data.tar.gz: 3dde7c0c2e713ebb419593eb6fb0d7634f07ee4d91e35549aa9fb57987a255e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8cf3e4c85acafb8161ff8a88d193b7bfde7e2b1ab241366364306a72416f462cbd31b4886a3b1f01402557a6045fd6ad40b1837849f307df9d3ac3fafdd7c6d0
|
7
|
+
data.tar.gz: a08db269e750b147daa7501cdf38dccdee8de7d07530b0112aecd1da3949b8daab704c778f1b3657d0dea328b087db9173f30e9aeb43f9d755d4a95dd1e51bac
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
- 0.17.0.pre7 - 10-01-2025
|
2
|
+
|
3
|
+
- Objects containing non serializable properties will return an Error object vs raising an exception. Ben Noordhuis
|
4
|
+
- Truffle support was added back Eregon
|
5
|
+
|
1
6
|
- 0.17.0.pre6 - 08-01-2025
|
2
7
|
|
3
8
|
- Moved all mini_racer interaction with v8 to a dedicated native thread to avoid cross VM stack contamination. Ben Noordhuis
|
data/README.md
CHANGED
@@ -206,58 +206,27 @@ context.eval("counter")
|
|
206
206
|
# => 1
|
207
207
|
```
|
208
208
|
|
209
|
-
###
|
209
|
+
### Garbage collection
|
210
210
|
|
211
|
-
|
211
|
+
Re-using the same context over and over again means V8's garbage collector will have to run to clean it up every now and then; it's possible to trigger a _blocking_ V8 GC run inside your context by running the `idle_notification` method on it, which takes a single argument: the amount of time (in milliseconds) that V8 should use at most for garbage collecting:
|
212
212
|
|
213
213
|
```ruby
|
214
|
-
|
215
|
-
|
216
|
-
context1 = MiniRacer::Context.new(isolate: isolate)
|
217
|
-
context2 = MiniRacer::Context.new(isolate: isolate)
|
218
|
-
|
219
|
-
context1.isolate == context2.isolate
|
220
|
-
# => true
|
221
|
-
```
|
222
|
-
|
223
|
-
The main benefit of this is avoiding creating/destroying isolates when not needed (for example if you use a lot of contexts).
|
224
|
-
|
225
|
-
The caveat with this is that a given isolate can only execute one context at a time, so don't share isolates across contexts that you want to run concurrently.
|
226
|
-
|
227
|
-
Also, note that if you want to use shared isolates together with snapshots, you need to first create an isolate with that snapshot, and then create contexts from that isolate:
|
228
|
-
|
229
|
-
```ruby
|
230
|
-
snapshot = MiniRacer::Snapshot.new("function hello() { return 'world!'; }")
|
231
|
-
|
232
|
-
isolate = MiniRacer::Isolate.new(snapshot)
|
233
|
-
|
234
|
-
context = MiniRacer::Context.new(isolate: isolate)
|
235
|
-
|
236
|
-
context.eval("hello()")
|
237
|
-
# => "world!"
|
238
|
-
```
|
239
|
-
|
240
|
-
Re-using the same isolate over and over again means V8's garbage collector will have to run to clean it up every now and then; it's possible to trigger a _blocking_ V8 GC run inside your isolate by running the `idle_notification` method on it, which takes a single argument: the amount of time (in milliseconds) that V8 should use at most for garbage collecting:
|
241
|
-
|
242
|
-
```ruby
|
243
|
-
isolate = MiniRacer::Isolate.new
|
244
|
-
|
245
|
-
context = MiniRacer::Context.new(isolate: isolate)
|
214
|
+
context = MiniRacer::Context.new
|
246
215
|
|
247
216
|
# do stuff with that context...
|
248
217
|
|
249
218
|
# give up to 100ms for V8 garbage collection
|
250
|
-
|
219
|
+
context.idle_notification(100)
|
251
220
|
|
252
221
|
# force V8 to perform a full GC
|
253
|
-
|
222
|
+
context.low_memory_notification
|
254
223
|
```
|
255
224
|
|
256
225
|
This can come in handy to force V8 GC runs for example in between requests if you use MiniRacer on a web application.
|
257
226
|
|
258
227
|
Note that this method maps directly to [`v8::Isolate::IdleNotification`](http://bespin.cz/~ondras/html/classv8_1_1Isolate.html#aea16cbb2e351de9a3ae7be2b7cb48297), and that in particular its return value is the same (true if there is no further garbage to collect, false otherwise) and the same caveats apply, in particular that `there is no guarantee that the [call will return] within the time limit.`
|
259
228
|
|
260
|
-
Additionally you may automate this process on a context by defining it with `MiniRacer::
|
229
|
+
Additionally you may automate this process on a context by defining it with `MiniRacer::Context.new(ensure_gc_after_idle: 1000)`. Using this will ensure V8 will run a full GC using `context.low_memory_notification` 1 second after the last eval on the context. Low memory notification is both slower and more aggressive than an idle_notification and will ensure long living contexts use minimal amounts of memory.
|
261
230
|
|
262
231
|
### V8 Runtime flags
|
263
232
|
|
@@ -301,7 +270,7 @@ Please refer to http://node.green/ as a reference on other harmony features.
|
|
301
270
|
|
302
271
|
A list of all V8 runtime flags can be found using `node --v8-options`, or else by perusing [the V8 source code for flags (make sure to use the right version of V8)](https://github.com/v8/v8/blob/master/src/flags/flag-definitions.h).
|
303
272
|
|
304
|
-
Note that runtime flags must be set before any other operation (e.g. creating a context
|
273
|
+
Note that runtime flags must be set before any other operation (e.g. creating a context or a snapshot), otherwise an exception will be thrown.
|
305
274
|
|
306
275
|
Flags:
|
307
276
|
|
@@ -34,8 +34,6 @@ struct State
|
|
34
34
|
v8::Local<v8::Context> safe_context;
|
35
35
|
v8::Persistent<v8::Context> persistent_context; // single-thread mode only
|
36
36
|
v8::Persistent<v8::Context> persistent_safe_context; // single-thread mode only
|
37
|
-
v8::Persistent<v8::Object> persistent_webassembly_instance;
|
38
|
-
v8::Local<v8::Object> webassembly_instance;
|
39
37
|
Context *ruby_context;
|
40
38
|
int64_t max_memory;
|
41
39
|
int err_reason;
|
@@ -81,21 +79,54 @@ bool reply(State& st, v8::Local<v8::Value> v)
|
|
81
79
|
return serialized.data != nullptr; // exception pending if false
|
82
80
|
}
|
83
81
|
|
82
|
+
bool reply(State& st, v8::Local<v8::Value> result, v8::Local<v8::Value> err)
|
83
|
+
{
|
84
|
+
v8::TryCatch try_catch(st.isolate);
|
85
|
+
try_catch.SetVerbose(st.verbose_exceptions);
|
86
|
+
v8::Local<v8::Array> response;
|
87
|
+
{
|
88
|
+
v8::Context::Scope context_scope(st.safe_context);
|
89
|
+
response = v8::Array::New(st.isolate, 2);
|
90
|
+
}
|
91
|
+
response->Set(st.context, 0, result).Check();
|
92
|
+
response->Set(st.context, 1, err).Check();
|
93
|
+
if (reply(st, response)) return true;
|
94
|
+
if (!try_catch.CanContinue()) { // termination exception?
|
95
|
+
try_catch.ReThrow();
|
96
|
+
return false;
|
97
|
+
}
|
98
|
+
v8::String::Utf8Value s(st.isolate, try_catch.Exception());
|
99
|
+
const char *message = *s ? *s : "unexpected failure";
|
100
|
+
// most serialization errors will be DataCloneErrors but not always
|
101
|
+
// DataCloneErrors are not directly detectable so use a heuristic
|
102
|
+
if (!strstr(message, "could not be cloned")) {
|
103
|
+
try_catch.ReThrow();
|
104
|
+
return false;
|
105
|
+
}
|
106
|
+
// return an {"error": "foo could not be cloned"} object
|
107
|
+
v8::Local<v8::Object> error;
|
108
|
+
{
|
109
|
+
v8::Context::Scope context_scope(st.safe_context);
|
110
|
+
error = v8::Object::New(st.isolate);
|
111
|
+
}
|
112
|
+
auto key = v8::String::NewFromUtf8Literal(st.isolate, "error");
|
113
|
+
v8::Local<v8::String> val;
|
114
|
+
if (!v8::String::NewFromUtf8(st.isolate, message).ToLocal(&val)) {
|
115
|
+
val = v8::String::NewFromUtf8Literal(st.isolate, "unexpected error");
|
116
|
+
}
|
117
|
+
error->Set(st.context, key, val).Check();
|
118
|
+
response->Set(st.context, 0, error).Check();
|
119
|
+
if (!reply(st, response)) {
|
120
|
+
try_catch.ReThrow();
|
121
|
+
return false;
|
122
|
+
}
|
123
|
+
return true;
|
124
|
+
}
|
125
|
+
|
84
126
|
v8::Local<v8::Value> sanitize(State& st, v8::Local<v8::Value> v)
|
85
127
|
{
|
86
128
|
// punch through proxies
|
87
129
|
while (v->IsProxy()) v = v8::Proxy::Cast(*v)->GetTarget();
|
88
|
-
// things that cannot be serialized
|
89
|
-
if (v->IsArgumentsObject() ||
|
90
|
-
v->IsPromise() ||
|
91
|
-
v->IsModule() ||
|
92
|
-
v->IsModuleNamespaceObject() ||
|
93
|
-
v->IsWasmMemoryObject() ||
|
94
|
-
v->IsWasmModuleObject() ||
|
95
|
-
v->IsWasmNull() ||
|
96
|
-
v->IsWeakRef()) {
|
97
|
-
return v8::Object::New(st.isolate);
|
98
|
-
}
|
99
130
|
// V8's serializer doesn't accept symbols
|
100
131
|
if (v->IsSymbol()) return v8::Symbol::Cast(*v)->Description(st.isolate);
|
101
132
|
// TODO(bnoordhuis) replace this hack with something more principled
|
@@ -111,17 +142,10 @@ v8::Local<v8::Value> sanitize(State& st, v8::Local<v8::Value> v)
|
|
111
142
|
return array;
|
112
143
|
}
|
113
144
|
}
|
114
|
-
// WebAssembly.Instance objects are not serializable but there
|
115
|
-
// is no direct way to detect them through the V8 C++ API
|
116
|
-
if (!st.webassembly_instance.IsEmpty() &&
|
117
|
-
v->IsObject() &&
|
118
|
-
v->InstanceOf(st.context, st.webassembly_instance).FromMaybe(false)) {
|
119
|
-
return v8::Object::New(st.isolate);
|
120
|
-
}
|
121
145
|
return v;
|
122
146
|
}
|
123
147
|
|
124
|
-
v8::Local<v8::
|
148
|
+
v8::Local<v8::String> to_error(State& st, v8::TryCatch *try_catch, int cause)
|
125
149
|
{
|
126
150
|
v8::Local<v8::Value> t;
|
127
151
|
char buf[1024];
|
@@ -216,18 +240,7 @@ extern "C" State *v8_thread_init(Context *c, const uint8_t *snapshot_buf,
|
|
216
240
|
st.safe_context = v8::Context::New(st.isolate);
|
217
241
|
st.context = v8::Context::New(st.isolate);
|
218
242
|
v8::Context::Scope context_scope(st.context);
|
219
|
-
auto global = st.context->Global();
|
220
|
-
// globalThis.WebAssembly is missing in --jitless mode
|
221
|
-
auto key = v8::String::NewFromUtf8Literal(st.isolate, "WebAssembly");
|
222
|
-
v8::Local<v8::Value> wasm_v;
|
223
|
-
if (global->Get(st.context, key).ToLocal(&wasm_v) && wasm_v->IsObject()) {
|
224
|
-
auto key = v8::String::NewFromUtf8Literal(st.isolate, "Instance");
|
225
|
-
st.webassembly_instance =
|
226
|
-
wasm_v.As<v8::Object>()
|
227
|
-
->Get(st.context, key).ToLocalChecked().As<v8::Object>();
|
228
|
-
}
|
229
243
|
if (single_threaded) {
|
230
|
-
st.persistent_webassembly_instance.Reset(st.isolate, st.webassembly_instance);
|
231
244
|
st.persistent_safe_context.Reset(st.isolate, st.safe_context);
|
232
245
|
st.persistent_context.Reset(st.isolate, st.context);
|
233
246
|
return pst; // intentionally returning early and keeping alive
|
@@ -246,7 +259,7 @@ void v8_api_callback(const v8::FunctionCallbackInfo<v8::Value>& info)
|
|
246
259
|
v8::Local<v8::Array> request;
|
247
260
|
{
|
248
261
|
v8::Context::Scope context_scope(st.safe_context);
|
249
|
-
request = v8::Array::New(st.isolate,
|
262
|
+
request = v8::Array::New(st.isolate, 1 + info.Length());
|
250
263
|
}
|
251
264
|
for (int i = 0, n = info.Length(); i < n; i++) {
|
252
265
|
request->Set(st.context, i, sanitize(st, info[i])).Check();
|
@@ -354,11 +367,6 @@ extern "C" void v8_call(State *pst, const uint8_t *p, size_t n)
|
|
354
367
|
v8::ValueDeserializer des(st.isolate, p, n);
|
355
368
|
std::vector<v8::Local<v8::Value>> args;
|
356
369
|
des.ReadHeader(st.context).Check();
|
357
|
-
v8::Local<v8::Array> response;
|
358
|
-
{
|
359
|
-
v8::Context::Scope context_scope(st.safe_context);
|
360
|
-
response = v8::Array::New(st.isolate, 2);
|
361
|
-
}
|
362
370
|
v8::Local<v8::Value> result;
|
363
371
|
int cause = INTERNAL_ERROR;
|
364
372
|
{
|
@@ -420,9 +428,7 @@ fail:
|
|
420
428
|
if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
|
421
429
|
if (cause) result = v8::Undefined(st.isolate);
|
422
430
|
auto err = to_error(st, &try_catch, cause);
|
423
|
-
|
424
|
-
response->Set(st.context, 1, err).Check();
|
425
|
-
if (!reply(st, response)) {
|
431
|
+
if (!reply(st, result, err)) {
|
426
432
|
assert(try_catch.HasCaught());
|
427
433
|
goto fail; // retry; can be termination exception
|
428
434
|
}
|
@@ -437,11 +443,6 @@ extern "C" void v8_eval(State *pst, const uint8_t *p, size_t n)
|
|
437
443
|
v8::HandleScope handle_scope(st.isolate);
|
438
444
|
v8::ValueDeserializer des(st.isolate, p, n);
|
439
445
|
des.ReadHeader(st.context).Check();
|
440
|
-
v8::Local<v8::Array> response;
|
441
|
-
{
|
442
|
-
v8::Context::Scope context_scope(st.safe_context);
|
443
|
-
response = v8::Array::New(st.isolate, 2);
|
444
|
-
}
|
445
446
|
v8::Local<v8::Value> result;
|
446
447
|
int cause = INTERNAL_ERROR;
|
447
448
|
{
|
@@ -475,9 +476,7 @@ fail:
|
|
475
476
|
if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
|
476
477
|
if (cause) result = v8::Undefined(st.isolate);
|
477
478
|
auto err = to_error(st, &try_catch, cause);
|
478
|
-
|
479
|
-
response->Set(st.context, 1, err).Check();
|
480
|
-
if (!reply(st, response)) {
|
479
|
+
if (!reply(st, result, err)) {
|
481
480
|
assert(try_catch.HasCaught());
|
482
481
|
goto fail; // retry; can be termination exception
|
483
482
|
}
|
@@ -638,11 +637,6 @@ extern "C" void v8_snapshot(State *pst, const uint8_t *p, size_t n)
|
|
638
637
|
v8::HandleScope handle_scope(st.isolate);
|
639
638
|
v8::ValueDeserializer des(st.isolate, p, n);
|
640
639
|
des.ReadHeader(st.context).Check();
|
641
|
-
v8::Local<v8::Array> response;
|
642
|
-
{
|
643
|
-
v8::Context::Scope context_scope(st.safe_context);
|
644
|
-
response = v8::Array::New(st.isolate, 2);
|
645
|
-
}
|
646
640
|
v8::Local<v8::Value> result;
|
647
641
|
v8::StartupData blob{nullptr, 0};
|
648
642
|
int cause = INTERNAL_ERROR;
|
@@ -682,9 +676,7 @@ fail:
|
|
682
676
|
} else {
|
683
677
|
err = to_error(st, &try_catch, cause);
|
684
678
|
}
|
685
|
-
|
686
|
-
response->Set(st.context, 1, err).Check();
|
687
|
-
if (!reply(st, response)) {
|
679
|
+
if (!reply(st, result, err)) {
|
688
680
|
assert(try_catch.HasCaught());
|
689
681
|
goto fail; // retry; can be termination exception
|
690
682
|
}
|
@@ -699,11 +691,6 @@ extern "C" void v8_warmup(State *pst, const uint8_t *p, size_t n)
|
|
699
691
|
std::vector<uint8_t> storage;
|
700
692
|
v8::ValueDeserializer des(st.isolate, p, n);
|
701
693
|
des.ReadHeader(st.context).Check();
|
702
|
-
v8::Local<v8::Array> response;
|
703
|
-
{
|
704
|
-
v8::Context::Scope context_scope(st.safe_context);
|
705
|
-
response = v8::Array::New(st.isolate, 2);
|
706
|
-
}
|
707
694
|
v8::Local<v8::Value> result;
|
708
695
|
v8::StartupData blob{nullptr, 0};
|
709
696
|
int cause = INTERNAL_ERROR;
|
@@ -761,9 +748,7 @@ fail:
|
|
761
748
|
} else {
|
762
749
|
err = to_error(st, &try_catch, cause);
|
763
750
|
}
|
764
|
-
|
765
|
-
response->Set(st.context, 1, err).Check();
|
766
|
-
if (!reply(st, response)) {
|
751
|
+
if (!reply(st, result, err)) {
|
767
752
|
assert(try_catch.HasCaught());
|
768
753
|
goto fail; // retry; can be termination exception
|
769
754
|
}
|
@@ -807,14 +792,12 @@ extern "C" void v8_single_threaded_enter(State *pst, Context *c, void (*f)(Conte
|
|
807
792
|
v8::Isolate::Scope isolate_scope(st.isolate);
|
808
793
|
v8::HandleScope handle_scope(st.isolate);
|
809
794
|
{
|
810
|
-
st.webassembly_instance = v8::Local<v8::Object>::New(st.isolate, st.persistent_webassembly_instance);
|
811
795
|
st.safe_context = v8::Local<v8::Context>::New(st.isolate, st.persistent_safe_context);
|
812
796
|
st.context = v8::Local<v8::Context>::New(st.isolate, st.persistent_context);
|
813
797
|
v8::Context::Scope context_scope(st.context);
|
814
798
|
f(c);
|
815
799
|
st.context = v8::Local<v8::Context>();
|
816
800
|
st.safe_context = v8::Local<v8::Context>();
|
817
|
-
st.webassembly_instance = v8::Local<v8::Object>();
|
818
801
|
}
|
819
802
|
}
|
820
803
|
|
@@ -830,7 +813,6 @@ State::~State()
|
|
830
813
|
{
|
831
814
|
v8::Locker locker(isolate);
|
832
815
|
v8::Isolate::Scope isolate_scope(isolate);
|
833
|
-
persistent_webassembly_instance.Reset();
|
834
816
|
persistent_safe_context.Reset();
|
835
817
|
persistent_context.Reset();
|
836
818
|
}
|
@@ -0,0 +1,380 @@
|
|
1
|
+
# This code used to be shared in lib/mini_racer.rb
|
2
|
+
# but was moved to the extension with https://github.com/rubyjs/mini_racer/pull/325.
|
3
|
+
# So now this is effectively duplicate logic with C/C++ code.
|
4
|
+
# Maybe one day it can be actually shared again between both backends.
|
5
|
+
|
6
|
+
module MiniRacer
|
7
|
+
|
8
|
+
MARSHAL_STACKDEPTH_DEFAULT = 2**9-2
|
9
|
+
MARSHAL_STACKDEPTH_MAX_VALUE = 2**10-2
|
10
|
+
|
11
|
+
class FailedV8Conversion
|
12
|
+
attr_reader :info
|
13
|
+
def initialize(info)
|
14
|
+
@info = info
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# helper class returned when we have a JavaScript function
|
19
|
+
class JavaScriptFunction
|
20
|
+
def to_s
|
21
|
+
"JavaScript Function"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Isolate
|
26
|
+
def initialize(snapshot = nil)
|
27
|
+
unless snapshot.nil? || snapshot.is_a?(Snapshot)
|
28
|
+
raise ArgumentError, "snapshot must be a Snapshot object, passed a #{snapshot.inspect}"
|
29
|
+
end
|
30
|
+
|
31
|
+
# defined in the C class
|
32
|
+
init_with_snapshot(snapshot)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Platform
|
37
|
+
class << self
|
38
|
+
def set_flags!(*args, **kwargs)
|
39
|
+
flags_to_strings([args, kwargs]).each do |flag|
|
40
|
+
# defined in the C class
|
41
|
+
set_flag_as_str!(flag)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def flags_to_strings(flags)
|
48
|
+
flags.flatten.map { |flag| flag_to_string(flag) }.flatten
|
49
|
+
end
|
50
|
+
|
51
|
+
# normalize flags to strings, and adds leading dashes if needed
|
52
|
+
def flag_to_string(flag)
|
53
|
+
if flag.is_a?(Hash)
|
54
|
+
flag.map do |key, value|
|
55
|
+
"#{flag_to_string(key)} #{value}"
|
56
|
+
end
|
57
|
+
else
|
58
|
+
str = flag.to_s
|
59
|
+
str = "--#{str}" unless str.start_with?('--')
|
60
|
+
str
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# eval is defined in the C class
|
67
|
+
class Context
|
68
|
+
|
69
|
+
class ExternalFunction
|
70
|
+
def initialize(name, callback, parent)
|
71
|
+
unless String === name
|
72
|
+
raise ArgumentError, "parent_object must be a String"
|
73
|
+
end
|
74
|
+
parent_object, _ , @name = name.rpartition(".")
|
75
|
+
@callback = callback
|
76
|
+
@parent = parent
|
77
|
+
@parent_object_eval = nil
|
78
|
+
@parent_object = nil
|
79
|
+
|
80
|
+
unless parent_object.empty?
|
81
|
+
@parent_object = parent_object
|
82
|
+
|
83
|
+
@parent_object_eval = ""
|
84
|
+
prev = ""
|
85
|
+
first = true
|
86
|
+
parent_object.split(".").each do |obj|
|
87
|
+
prev << obj
|
88
|
+
if first
|
89
|
+
@parent_object_eval << "if (typeof #{prev} !== 'object' || typeof #{prev} !== 'function') { #{prev} = {} };\n"
|
90
|
+
else
|
91
|
+
@parent_object_eval << "#{prev} = #{prev} || {};\n"
|
92
|
+
end
|
93
|
+
prev << "."
|
94
|
+
first = false
|
95
|
+
end
|
96
|
+
@parent_object_eval << "#{parent_object};"
|
97
|
+
end
|
98
|
+
notify_v8
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def initialize(max_memory: nil, timeout: nil, isolate: nil, ensure_gc_after_idle: nil, snapshot: nil, marshal_stack_depth: nil)
|
103
|
+
options ||= {}
|
104
|
+
|
105
|
+
check_init_options!(isolate: isolate, snapshot: snapshot, max_memory: max_memory, marshal_stack_depth: marshal_stack_depth, ensure_gc_after_idle: ensure_gc_after_idle, timeout: timeout)
|
106
|
+
|
107
|
+
@functions = {}
|
108
|
+
@timeout = nil
|
109
|
+
@max_memory = nil
|
110
|
+
@current_exception = nil
|
111
|
+
@timeout = timeout
|
112
|
+
@max_memory = max_memory
|
113
|
+
@marshal_stack_depth = marshal_stack_depth
|
114
|
+
|
115
|
+
# false signals it should be fetched if requested
|
116
|
+
@isolate = isolate || false
|
117
|
+
|
118
|
+
@ensure_gc_after_idle = ensure_gc_after_idle
|
119
|
+
|
120
|
+
if @ensure_gc_after_idle
|
121
|
+
@last_eval = nil
|
122
|
+
@ensure_gc_thread = nil
|
123
|
+
@ensure_gc_mutex = Mutex.new
|
124
|
+
end
|
125
|
+
|
126
|
+
@disposed = false
|
127
|
+
|
128
|
+
@callback_mutex = Mutex.new
|
129
|
+
@callback_running = false
|
130
|
+
@thread_raise_called = false
|
131
|
+
@eval_thread = nil
|
132
|
+
|
133
|
+
# defined in the C class
|
134
|
+
init_unsafe(isolate, snapshot)
|
135
|
+
end
|
136
|
+
|
137
|
+
def isolate
|
138
|
+
return @isolate if @isolate != false
|
139
|
+
# defined in the C class
|
140
|
+
@isolate = create_isolate_value
|
141
|
+
end
|
142
|
+
|
143
|
+
def eval(str, options=nil)
|
144
|
+
raise(ContextDisposedError, 'attempted to call eval on a disposed context!') if @disposed
|
145
|
+
|
146
|
+
filename = options && options[:filename].to_s
|
147
|
+
|
148
|
+
@eval_thread = Thread.current
|
149
|
+
isolate_mutex.synchronize do
|
150
|
+
@current_exception = nil
|
151
|
+
timeout do
|
152
|
+
eval_unsafe(str, filename)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
ensure
|
156
|
+
@eval_thread = nil
|
157
|
+
ensure_gc_thread if @ensure_gc_after_idle
|
158
|
+
end
|
159
|
+
|
160
|
+
def call(function_name, *arguments)
|
161
|
+
raise(ContextDisposedError, 'attempted to call function on a disposed context!') if @disposed
|
162
|
+
|
163
|
+
@eval_thread = Thread.current
|
164
|
+
isolate_mutex.synchronize do
|
165
|
+
timeout do
|
166
|
+
call_unsafe(function_name, *arguments)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
ensure
|
170
|
+
@eval_thread = nil
|
171
|
+
ensure_gc_thread if @ensure_gc_after_idle
|
172
|
+
end
|
173
|
+
|
174
|
+
def dispose
|
175
|
+
return if @disposed
|
176
|
+
isolate_mutex.synchronize do
|
177
|
+
return if @disposed
|
178
|
+
dispose_unsafe
|
179
|
+
@disposed = true
|
180
|
+
@isolate = nil # allow it to be garbage collected, if set
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
def attach(name, callback)
|
186
|
+
raise(ContextDisposedError, 'attempted to call function on a disposed context!') if @disposed
|
187
|
+
|
188
|
+
wrapped = lambda do |*args|
|
189
|
+
begin
|
190
|
+
|
191
|
+
r = nil
|
192
|
+
|
193
|
+
begin
|
194
|
+
@callback_mutex.synchronize{
|
195
|
+
@callback_running = true
|
196
|
+
}
|
197
|
+
r = callback.call(*args)
|
198
|
+
ensure
|
199
|
+
@callback_mutex.synchronize{
|
200
|
+
@callback_running = false
|
201
|
+
}
|
202
|
+
end
|
203
|
+
|
204
|
+
# wait up to 2 seconds for this to be interrupted
|
205
|
+
# will very rarely be called cause #raise is called
|
206
|
+
# in another mutex
|
207
|
+
@callback_mutex.synchronize {
|
208
|
+
if @thread_raise_called
|
209
|
+
sleep 2
|
210
|
+
end
|
211
|
+
}
|
212
|
+
|
213
|
+
r
|
214
|
+
|
215
|
+
ensure
|
216
|
+
@callback_mutex.synchronize {
|
217
|
+
@thread_raise_called = false
|
218
|
+
}
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
isolate_mutex.synchronize do
|
223
|
+
external = ExternalFunction.new(name, wrapped, self)
|
224
|
+
@functions["#{name}"] = external
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
private
|
229
|
+
|
230
|
+
def ensure_gc_thread
|
231
|
+
@last_eval = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
232
|
+
@ensure_gc_mutex.synchronize do
|
233
|
+
@ensure_gc_thread = nil if !@ensure_gc_thread&.alive?
|
234
|
+
return if !Thread.main.alive? # avoid "can't alloc thread" exception
|
235
|
+
@ensure_gc_thread ||= Thread.new do
|
236
|
+
ensure_gc_after_idle_seconds = @ensure_gc_after_idle / 1000.0
|
237
|
+
done = false
|
238
|
+
while !done
|
239
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
240
|
+
|
241
|
+
if @disposed
|
242
|
+
@ensure_gc_thread = nil
|
243
|
+
break
|
244
|
+
end
|
245
|
+
|
246
|
+
if !@eval_thread && ensure_gc_after_idle_seconds < now - @last_eval
|
247
|
+
@ensure_gc_mutex.synchronize do
|
248
|
+
isolate_mutex.synchronize do
|
249
|
+
if !@eval_thread
|
250
|
+
low_memory_notification if !@disposed
|
251
|
+
@ensure_gc_thread = nil
|
252
|
+
done = true
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
sleep ensure_gc_after_idle_seconds if !done
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def stop_attached
|
264
|
+
@callback_mutex.synchronize{
|
265
|
+
if @callback_running
|
266
|
+
@eval_thread.raise ScriptTerminatedError, "Terminated during callback"
|
267
|
+
@thread_raise_called = true
|
268
|
+
end
|
269
|
+
}
|
270
|
+
end
|
271
|
+
|
272
|
+
def timeout(&blk)
|
273
|
+
return blk.call unless @timeout
|
274
|
+
|
275
|
+
mutex = Mutex.new
|
276
|
+
done = false
|
277
|
+
|
278
|
+
rp,wp = IO.pipe
|
279
|
+
|
280
|
+
t = Thread.new do
|
281
|
+
begin
|
282
|
+
result = rp.wait_readable(@timeout/1000.0)
|
283
|
+
if !result
|
284
|
+
mutex.synchronize do
|
285
|
+
stop unless done
|
286
|
+
end
|
287
|
+
end
|
288
|
+
rescue => e
|
289
|
+
STDERR.puts e
|
290
|
+
STDERR.puts "FAILED TO TERMINATE DUE TO TIMEOUT"
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
rval = blk.call
|
295
|
+
mutex.synchronize do
|
296
|
+
done = true
|
297
|
+
end
|
298
|
+
|
299
|
+
wp.close
|
300
|
+
|
301
|
+
# ensure we do not leak a thread in state
|
302
|
+
t.join
|
303
|
+
t = nil
|
304
|
+
|
305
|
+
rval
|
306
|
+
ensure
|
307
|
+
# exceptions need to be handled
|
308
|
+
wp&.close
|
309
|
+
t&.join
|
310
|
+
rp&.close
|
311
|
+
end
|
312
|
+
|
313
|
+
def check_init_options!(isolate:, snapshot:, max_memory:, marshal_stack_depth:, ensure_gc_after_idle:, timeout:)
|
314
|
+
assert_option_is_nil_or_a('isolate', isolate, Isolate)
|
315
|
+
assert_option_is_nil_or_a('snapshot', snapshot, Snapshot)
|
316
|
+
|
317
|
+
assert_numeric_or_nil('max_memory', max_memory, min_value: 10_000, max_value: 2**32-1)
|
318
|
+
assert_numeric_or_nil('marshal_stack_depth', marshal_stack_depth, min_value: 1, max_value: MARSHAL_STACKDEPTH_MAX_VALUE)
|
319
|
+
assert_numeric_or_nil('ensure_gc_after_idle', ensure_gc_after_idle, min_value: 1)
|
320
|
+
assert_numeric_or_nil('timeout', timeout, min_value: 1)
|
321
|
+
|
322
|
+
if isolate && snapshot
|
323
|
+
raise ArgumentError, 'can only pass one of isolate and snapshot options'
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
def assert_numeric_or_nil(option_name, object, min_value:, max_value: nil)
|
328
|
+
if max_value && object.is_a?(Numeric) && object > max_value
|
329
|
+
raise ArgumentError, "#{option_name} must be less than or equal to #{max_value}"
|
330
|
+
end
|
331
|
+
|
332
|
+
if object.is_a?(Numeric) && object < min_value
|
333
|
+
raise ArgumentError, "#{option_name} must be larger than or equal to #{min_value}"
|
334
|
+
end
|
335
|
+
|
336
|
+
if !object.nil? && !object.is_a?(Numeric)
|
337
|
+
raise ArgumentError, "#{option_name} must be a number, passed a #{object.inspect}"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def assert_option_is_nil_or_a(option_name, object, klass)
|
342
|
+
unless object.nil? || object.is_a?(klass)
|
343
|
+
raise ArgumentError, "#{option_name} must be a #{klass} object, passed a #{object.inspect}"
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# `size` and `warmup!` public methods are defined in the C class
|
349
|
+
class Snapshot
|
350
|
+
def initialize(str = '')
|
351
|
+
# ensure it first can load
|
352
|
+
begin
|
353
|
+
ctx = MiniRacer::Context.new
|
354
|
+
ctx.eval(str)
|
355
|
+
rescue MiniRacer::RuntimeError => e
|
356
|
+
raise MiniRacer::SnapshotError, e.message, e.backtrace
|
357
|
+
end
|
358
|
+
|
359
|
+
@source = str
|
360
|
+
|
361
|
+
# defined in the C class
|
362
|
+
load(str)
|
363
|
+
end
|
364
|
+
|
365
|
+
def warmup!(src)
|
366
|
+
# we have to do something here
|
367
|
+
# we are bloating memory a bit but it is more correct
|
368
|
+
# than hitting an exception when attempty to compile invalid source
|
369
|
+
begin
|
370
|
+
ctx = MiniRacer::Context.new
|
371
|
+
ctx.eval(@source)
|
372
|
+
ctx.eval(src)
|
373
|
+
rescue MiniRacer::RuntimeError => e
|
374
|
+
raise MiniRacer::SnapshotError, e.message, e.backtrace
|
375
|
+
end
|
376
|
+
|
377
|
+
warmup_unsafe!(src)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'shared'
|
4
|
+
|
3
5
|
module MiniRacer
|
4
6
|
|
5
7
|
class Context
|
@@ -43,6 +45,7 @@ module MiniRacer
|
|
43
45
|
end
|
44
46
|
|
45
47
|
def heap_stats
|
48
|
+
raise ContextDisposedError if @disposed
|
46
49
|
{
|
47
50
|
total_physical_size: 0,
|
48
51
|
total_heap_size_executable: 0,
|
@@ -60,6 +63,14 @@ module MiniRacer
|
|
60
63
|
end
|
61
64
|
end
|
62
65
|
|
66
|
+
def low_memory_notification
|
67
|
+
GC.start
|
68
|
+
end
|
69
|
+
|
70
|
+
def idle_notification(idle_time)
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
63
74
|
private
|
64
75
|
|
65
76
|
@context_initialized = false
|
@@ -95,9 +106,11 @@ module MiniRacer
|
|
95
106
|
else
|
96
107
|
@snapshot = nil
|
97
108
|
end
|
98
|
-
@is_object_or_array_func, @is_time_func, @js_date_to_time_func, @is_symbol_func, @js_symbol_to_symbol_func, @js_new_date_func, @js_new_array_func = eval_in_context <<-CODE
|
109
|
+
@is_object_or_array_func, @is_map_func, @is_map_iterator_func, @is_time_func, @js_date_to_time_func, @is_symbol_func, @js_symbol_to_symbol_func, @js_new_date_func, @js_new_array_func = eval_in_context <<-CODE
|
99
110
|
[
|
100
111
|
(x) => { return (x instanceof Object || x instanceof Array) && !(x instanceof Date) && !(x instanceof Function) },
|
112
|
+
(x) => { return x instanceof Map },
|
113
|
+
(x) => { return x[Symbol.toStringTag] === 'Map Iterator' },
|
101
114
|
(x) => { return x instanceof Date },
|
102
115
|
(x) => { return x.getTime(x) },
|
103
116
|
(x) => { return typeof x === 'symbol' },
|
@@ -233,6 +246,10 @@ module MiniRacer
|
|
233
246
|
js_date_to_time(value)
|
234
247
|
elsif symbol?(value)
|
235
248
|
js_symbol_to_symbol(value)
|
249
|
+
elsif map?(value)
|
250
|
+
js_map_to_hash(value)
|
251
|
+
elsif map_iterator?(value)
|
252
|
+
value.map { |e| convert_js_to_ruby(e) }
|
236
253
|
else
|
237
254
|
object = value
|
238
255
|
h = {}
|
@@ -251,6 +268,14 @@ module MiniRacer
|
|
251
268
|
@is_object_or_array_func.call(val)
|
252
269
|
end
|
253
270
|
|
271
|
+
def map?(value)
|
272
|
+
@is_map_func.call(value)
|
273
|
+
end
|
274
|
+
|
275
|
+
def map_iterator?(value)
|
276
|
+
@is_map_iterator_func.call(value)
|
277
|
+
end
|
278
|
+
|
254
279
|
def time?(value)
|
255
280
|
@is_time_func.call(value)
|
256
281
|
end
|
@@ -268,6 +293,12 @@ module MiniRacer
|
|
268
293
|
@js_symbol_to_symbol_func.call(value).to_s.to_sym
|
269
294
|
end
|
270
295
|
|
296
|
+
def js_map_to_hash(map)
|
297
|
+
map.to_a.to_h do |key, value|
|
298
|
+
[convert_js_to_ruby(key), convert_js_to_ruby(value)]
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
271
302
|
def js_new_date(value)
|
272
303
|
@js_new_date_func.call(value)
|
273
304
|
end
|
@@ -320,48 +351,13 @@ module MiniRacer
|
|
320
351
|
# However, isolate can hold a snapshot, and code and ASTs are shared between contexts.
|
321
352
|
@snapshot = snapshot
|
322
353
|
end
|
323
|
-
|
324
|
-
def low_memory_notification
|
325
|
-
GC.start
|
326
|
-
end
|
327
|
-
|
328
|
-
def idle_notification(idle_time)
|
329
|
-
true
|
330
|
-
end
|
331
354
|
end
|
332
355
|
|
333
356
|
class Platform
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
private
|
342
|
-
|
343
|
-
def flags_to_strings(flags)
|
344
|
-
flags.flatten.map { |flag| flag_to_string(flag) }.flatten
|
345
|
-
end
|
346
|
-
|
347
|
-
# normalize flags to strings, and adds leading dashes if needed
|
348
|
-
def flag_to_string(flag)
|
349
|
-
if flag.is_a?(Hash)
|
350
|
-
flag.map do |key, value|
|
351
|
-
"#{flag_to_string(key)} #{value}"
|
352
|
-
end
|
353
|
-
else
|
354
|
-
str = flag.to_s
|
355
|
-
str = "--#{str}" unless str.start_with?('--')
|
356
|
-
str
|
357
|
-
end
|
358
|
-
end
|
359
|
-
|
360
|
-
def set_flag_as_str!(flag)
|
361
|
-
raise TypeError, "wrong type argument #{flag.class} (should be a string)" unless flag.is_a?(String)
|
362
|
-
raise MiniRacer::PlatformAlreadyInitialized, "The platform is already initialized." if Context.instance_variable_get(:@context_initialized)
|
363
|
-
Context.instance_variable_set(:@use_strict, true) if "--use_strict" == flag
|
364
|
-
end
|
357
|
+
def self.set_flag_as_str!(flag)
|
358
|
+
raise TypeError, "wrong type argument #{flag.class} (should be a string)" unless flag.is_a?(String)
|
359
|
+
raise MiniRacer::PlatformAlreadyInitialized, "The platform is already initialized." if Context.instance_variable_get(:@context_initialized)
|
360
|
+
Context.instance_variable_set(:@use_strict, true) if "--use_strict" == flag
|
365
361
|
end
|
366
362
|
end
|
367
363
|
|
data/lib/mini_racer/version.rb
CHANGED
data/lib/mini_racer.rb
CHANGED
@@ -75,14 +75,12 @@ module MiniRacer
|
|
75
75
|
|
76
76
|
if String === file_or_io
|
77
77
|
f = File.open(file_or_io, "w")
|
78
|
-
|
78
|
+
implicit = true
|
79
79
|
else
|
80
80
|
f = file_or_io
|
81
81
|
end
|
82
82
|
|
83
|
-
|
84
|
-
raise ArgumentError, "file_or_io"
|
85
|
-
end
|
83
|
+
raise ArgumentError, "file_or_io" unless File === f
|
86
84
|
|
87
85
|
f.write(heap_snapshot())
|
88
86
|
ensure
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mini_racer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.17.0.
|
4
|
+
version: 0.17.0.pre7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Saffron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-01-
|
11
|
+
date: 2025-01-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -115,6 +115,7 @@ files:
|
|
115
115
|
- ext/mini_racer_loader/extconf.rb
|
116
116
|
- ext/mini_racer_loader/mini_racer_loader.c
|
117
117
|
- lib/mini_racer.rb
|
118
|
+
- lib/mini_racer/shared.rb
|
118
119
|
- lib/mini_racer/truffleruby.rb
|
119
120
|
- lib/mini_racer/version.rb
|
120
121
|
homepage: https://github.com/discourse/mini_racer
|
@@ -122,9 +123,9 @@ licenses:
|
|
122
123
|
- MIT
|
123
124
|
metadata:
|
124
125
|
bug_tracker_uri: https://github.com/discourse/mini_racer/issues
|
125
|
-
changelog_uri: https://github.com/discourse/mini_racer/blob/v0.17.0.
|
126
|
-
documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.17.0.
|
127
|
-
source_code_uri: https://github.com/discourse/mini_racer/tree/v0.17.0.
|
126
|
+
changelog_uri: https://github.com/discourse/mini_racer/blob/v0.17.0.pre7/CHANGELOG
|
127
|
+
documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.17.0.pre7
|
128
|
+
source_code_uri: https://github.com/discourse/mini_racer/tree/v0.17.0.pre7
|
128
129
|
post_install_message:
|
129
130
|
rdoc_options: []
|
130
131
|
require_paths:
|