mini_racer 0.17.0.pre6 → 0.17.0.pre7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd376af6f7b025e07395d6e1a9c06c83007bc2f8bc7e51abbfd2312605bd0721
4
- data.tar.gz: 443568af7a5d3aea76bd354643f32bc39454e818cb9ae86ed4b34214fdd9c612
3
+ metadata.gz: 1d058a397309a0e5354576fa25b299f26e9ca9576b23333063dbcd46fcf9f775
4
+ data.tar.gz: 3dde7c0c2e713ebb419593eb6fb0d7634f07ee4d91e35549aa9fb57987a255e5
5
5
  SHA512:
6
- metadata.gz: b63e0bbd1e8fc79a882f8cbc7b8541a0e4f76d65c824a6cedca617e46f3395af4b409484e558fefa5ef0259bd6b1588a13e44c63131e11472e5fb28ff6f34f88
7
- data.tar.gz: 228ad46872d41a852498c7ac204b2b9fa664903e394d915dcdd53852ea3a2053d17818c1b04fa2ff8a40aef9c464c207b3cb72a5b076ba8a7a09a6f6a7f36848
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
- ### Shared isolates
209
+ ### Garbage collection
210
210
 
211
- By default, MiniRacer's contexts each have their own isolate (V8 runtime). For efficiency, it is possible to re-use an isolate across contexts:
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
- isolate = MiniRacer::Isolate.new
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
- isolate.idle_notification(100)
219
+ context.idle_notification(100)
251
220
 
252
221
  # force V8 to perform a full GC
253
- isolate.low_memory_notification
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::Content.new(ensure_gc_after_idle: 1000)`. Using this will ensure V8 will run a full GC using `context.isolate.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 isolates use minimal amounts of memory.
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, a snapshot or an isolate), otherwise an exception will be thrown.
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::Value> to_error(State& st, v8::TryCatch *try_catch, int cause)
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, 2);
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
- response->Set(st.context, 0, result).Check();
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
- response->Set(st.context, 0, result).Check();
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
- response->Set(st.context, 0, result).Check();
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
- response->Set(st.context, 0, result).Check();
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
- class << self
335
- def set_flags!(*args, **kwargs)
336
- flags_to_strings([args, kwargs]).each do |flag|
337
- set_flag_as_str!(flag)
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
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniRacer
4
- VERSION = "0.17.0.pre6"
4
+ VERSION = "0.17.0.pre7"
5
5
  LIBV8_NODE_VERSION = "~> 22.7.0.4"
6
6
  end
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
- implicit = true
78
+ implicit = true
79
79
  else
80
80
  f = file_or_io
81
81
  end
82
82
 
83
- if !(File === f)
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.pre6
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-08 00:00:00.000000000 Z
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.pre6/CHANGELOG
126
- documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.17.0.pre6
127
- source_code_uri: https://github.com/discourse/mini_racer/tree/v0.17.0.pre6
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: