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 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: