mini_racer 0.1.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,35 +1,109 @@
1
1
  #include <stdio.h>
2
2
  #include <ruby.h>
3
3
  #include <ruby/thread.h>
4
- #include <include/v8.h>
5
- #include <include/libplatform/libplatform.h>
4
+ #include <ruby/io.h>
5
+ #include <v8.h>
6
+ #include <v8-profiler.h>
7
+ #include <libplatform/libplatform.h>
6
8
  #include <ruby/encoding.h>
7
9
  #include <pthread.h>
8
10
  #include <unistd.h>
11
+ #include <mutex>
12
+ #include <atomic>
13
+ #include <math.h>
14
+ #include <errno.h>
9
15
 
10
16
  using namespace v8;
11
17
 
12
- class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
13
- public:
14
- virtual void* Allocate(size_t length) {
15
- void* data = AllocateUninitialized(length);
16
- return data == NULL ? data : memset(data, 0, length);
17
- }
18
- virtual void* AllocateUninitialized(size_t length) { return malloc(length); }
19
- virtual void Free(void* data, size_t) { free(data); }
18
+ typedef struct {
19
+ const char* data;
20
+ int raw_size;
21
+ } SnapshotInfo;
22
+
23
+ class IsolateInfo {
24
+ public:
25
+ Isolate* isolate;
26
+ ArrayBuffer::Allocator* allocator;
27
+ StartupData* startup_data;
28
+ bool interrupted;
29
+ bool added_gc_cb;
30
+ pid_t pid;
31
+ VALUE mutex;
32
+
33
+ class Lock {
34
+ VALUE &mutex;
35
+
36
+ public:
37
+ Lock(VALUE &mutex) : mutex(mutex) {
38
+ rb_mutex_lock(mutex);
39
+ }
40
+ ~Lock() {
41
+ rb_mutex_unlock(mutex);
42
+ }
43
+ };
44
+
45
+
46
+ IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
47
+ interrupted(false), added_gc_cb(false), pid(getpid()), refs_count(0) {
48
+ VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
49
+ mutex = rb_class_new_instance(0, nullptr, cMutex);
50
+ }
51
+
52
+ ~IsolateInfo();
53
+
54
+ void init(SnapshotInfo* snapshot_info = nullptr);
55
+
56
+ void mark() {
57
+ rb_gc_mark(mutex);
58
+ }
59
+
60
+ Lock createLock() {
61
+ Lock lock(mutex);
62
+ return lock;
63
+ }
64
+
65
+ void hold() {
66
+ refs_count++;
67
+ }
68
+ void release() {
69
+ if (--refs_count <= 0) {
70
+ delete this;
71
+ }
72
+ }
73
+
74
+ int refs() {
75
+ return refs_count;
76
+ }
77
+
78
+ static void* operator new(size_t size) {
79
+ return ruby_xmalloc(size);
80
+ }
81
+
82
+ static void operator delete(void *block) {
83
+ xfree(block);
84
+ }
85
+ private:
86
+ // how many references to this isolate exist
87
+ // we can't rely on Ruby's GC for this, because Ruby could destroy the
88
+ // isolate before destroying the contexts that depend on them. We'd need to
89
+ // keep a list of linked contexts in the isolate to destroy those first when
90
+ // isolate destruction was requested. Keeping such a list would require
91
+ // notification from the context VALUEs when they are constructed and
92
+ // destroyed. With a ref count, those notifications are still needed, but
93
+ // we keep a simple int rather than a list of pointers.
94
+ std::atomic_int refs_count;
20
95
  };
21
96
 
22
97
  typedef struct {
23
- Isolate* isolate;
98
+ IsolateInfo* isolate_info;
24
99
  Persistent<Context>* context;
25
- ArrayBufferAllocator* allocator;
26
- bool interrupted;
27
100
  } ContextInfo;
28
101
 
29
102
  typedef struct {
30
103
  bool parsed;
31
104
  bool executed;
32
105
  bool terminated;
106
+ bool json;
33
107
  Persistent<Value>* value;
34
108
  Persistent<Value>* message;
35
109
  Persistent<Value>* backtrace;
@@ -38,168 +112,598 @@ typedef struct {
38
112
  typedef struct {
39
113
  ContextInfo* context_info;
40
114
  Local<String>* eval;
115
+ Local<String>* filename;
41
116
  useconds_t timeout;
42
117
  EvalResult* result;
118
+ size_t max_memory;
119
+ size_t marshal_stackdepth;
43
120
  } EvalParams;
44
121
 
122
+ typedef struct {
123
+ ContextInfo *context_info;
124
+ char *function_name;
125
+ int argc;
126
+ bool error;
127
+ Local<Function> fun;
128
+ Local<Value> *argv;
129
+ EvalResult result;
130
+ size_t max_memory;
131
+ size_t marshal_stackdepth;
132
+ } FunctionCall;
133
+
134
+ class IsolateData {
135
+ public:
136
+ enum Flag {
137
+ // first flags are bitfield
138
+ // max count: sizeof(uintptr_t) * 8
139
+ IN_GVL, // whether we are inside of ruby gvl or not
140
+ DO_TERMINATE, // terminate as soon as possible
141
+ MEM_SOFTLIMIT_REACHED, // we've hit the memory soft limit
142
+ MEM_SOFTLIMIT_MAX, // maximum memory value
143
+ MARSHAL_STACKDEPTH_REACHED, // we've hit our max stack depth
144
+ MARSHAL_STACKDEPTH_VALUE, // current stackdepth
145
+ MARSHAL_STACKDEPTH_MAX, // maximum stack depth during marshal
146
+ };
147
+
148
+ static void Init(Isolate *isolate) {
149
+ // zero out all fields in the bitfield
150
+ isolate->SetData(0, 0);
151
+ }
152
+
153
+ static uintptr_t Get(Isolate *isolate, Flag flag) {
154
+ Bitfield u = { reinterpret_cast<uint64_t>(isolate->GetData(0)) };
155
+ switch (flag) {
156
+ case IN_GVL: return u.IN_GVL;
157
+ case DO_TERMINATE: return u.DO_TERMINATE;
158
+ case MEM_SOFTLIMIT_REACHED: return u.MEM_SOFTLIMIT_REACHED;
159
+ case MEM_SOFTLIMIT_MAX: return static_cast<uintptr_t>(u.MEM_SOFTLIMIT_MAX) << 10;
160
+ case MARSHAL_STACKDEPTH_REACHED: return u.MARSHAL_STACKDEPTH_REACHED;
161
+ case MARSHAL_STACKDEPTH_VALUE: return u.MARSHAL_STACKDEPTH_VALUE;
162
+ case MARSHAL_STACKDEPTH_MAX: return u.MARSHAL_STACKDEPTH_MAX;
163
+ }
164
+
165
+ // avoid compiler warning
166
+ return u.IN_GVL;
167
+ }
168
+
169
+ static void Set(Isolate *isolate, Flag flag, uintptr_t value) {
170
+ Bitfield u = { reinterpret_cast<uint64_t>(isolate->GetData(0)) };
171
+ switch (flag) {
172
+ case IN_GVL: u.IN_GVL = value; break;
173
+ case DO_TERMINATE: u.DO_TERMINATE = value; break;
174
+ case MEM_SOFTLIMIT_REACHED: u.MEM_SOFTLIMIT_REACHED = value; break;
175
+ // drop least significant 10 bits 'store memory amount in kb'
176
+ case MEM_SOFTLIMIT_MAX: u.MEM_SOFTLIMIT_MAX = value >> 10; break;
177
+ case MARSHAL_STACKDEPTH_REACHED: u.MARSHAL_STACKDEPTH_REACHED = value; break;
178
+ case MARSHAL_STACKDEPTH_VALUE: u.MARSHAL_STACKDEPTH_VALUE = value; break;
179
+ case MARSHAL_STACKDEPTH_MAX: u.MARSHAL_STACKDEPTH_MAX = value; break;
180
+ }
181
+ isolate->SetData(0, reinterpret_cast<void*>(u.dataPtr));
182
+ }
183
+
184
+ private:
185
+ struct Bitfield {
186
+ // WARNING: this would explode on platforms below 64 bit ptrs
187
+ // compiler will fail here, making it clear for them.
188
+ // Additionally, using the other part of the union to reinterpret the
189
+ // memory is undefined behavior according to spec, but is / has been stable
190
+ // across major compilers for decades.
191
+ static_assert(sizeof(uintptr_t) >= sizeof(uint64_t), "mini_racer not supported on this platform. ptr size must be at least 64 bit.");
192
+ union {
193
+ uint64_t dataPtr: 64;
194
+ // order in this struct matters. For cpu performance keep larger subobjects
195
+ // aligned on their boundaries (8 16 32), try not to straddle
196
+ struct {
197
+ size_t MEM_SOFTLIMIT_MAX:22;
198
+ bool IN_GVL:1;
199
+ bool DO_TERMINATE:1;
200
+ bool MEM_SOFTLIMIT_REACHED:1;
201
+ bool MARSHAL_STACKDEPTH_REACHED:1;
202
+ uint8_t :0; // align to next 8bit bound
203
+ size_t MARSHAL_STACKDEPTH_VALUE:10;
204
+ uint8_t :0; // align to next 8bit bound
205
+ size_t MARSHAL_STACKDEPTH_MAX:10;
206
+ };
207
+ };
208
+ };
209
+ };
210
+
211
+ struct StackCounter {
212
+ static void Reset(Isolate* isolate) {
213
+ if (IsolateData::Get(isolate, IsolateData::MARSHAL_STACKDEPTH_MAX) > 0) {
214
+ IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE, 0);
215
+ IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED, false);
216
+ }
217
+ }
218
+
219
+ static void SetMax(Isolate* isolate, size_t marshalMaxStackDepth) {
220
+ if (marshalMaxStackDepth > 0) {
221
+ IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_MAX, marshalMaxStackDepth);
222
+ IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE, 0);
223
+ IsolateData::Set(isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED, false);
224
+ }
225
+ }
226
+
227
+ StackCounter(Isolate* isolate) {
228
+ this->isActive = IsolateData::Get(isolate, IsolateData::MARSHAL_STACKDEPTH_MAX) > 0;
229
+
230
+ if (this->isActive) {
231
+ this->isolate = isolate;
232
+ this->IncDepth(1);
233
+ }
234
+ }
235
+
236
+ bool IsTooDeep() {
237
+ if (!this->IsActive()) {
238
+ return false;
239
+ }
240
+
241
+ size_t depth = IsolateData::Get(this->isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE);
242
+ size_t maxDepth = IsolateData::Get(this->isolate, IsolateData::MARSHAL_STACKDEPTH_MAX);
243
+ if (depth > maxDepth) {
244
+ IsolateData::Set(this->isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED, true);
245
+ return true;
246
+ }
247
+
248
+ return false;
249
+ }
250
+
251
+ bool IsActive() {
252
+ return this->isActive && !IsolateData::Get(this->isolate, IsolateData::DO_TERMINATE);
253
+ }
254
+
255
+ ~StackCounter() {
256
+ if (this->IsActive()) {
257
+ this->IncDepth(-1);
258
+ }
259
+ }
260
+
261
+ private:
262
+ Isolate* isolate;
263
+ bool isActive;
264
+
265
+ void IncDepth(int direction) {
266
+ int inc = direction > 0 ? 1 : -1;
267
+
268
+ size_t depth = IsolateData::Get(this->isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE);
269
+
270
+ // don't decrement past 0
271
+ if (inc > 0 || depth > 0) {
272
+ depth += inc;
273
+ }
274
+
275
+ IsolateData::Set(this->isolate, IsolateData::MARSHAL_STACKDEPTH_VALUE, depth);
276
+ }
277
+ };
278
+
279
+ static VALUE rb_cContext;
280
+ static VALUE rb_cSnapshot;
281
+ static VALUE rb_cIsolate;
282
+
45
283
  static VALUE rb_eScriptTerminatedError;
284
+ static VALUE rb_eV8OutOfMemoryError;
46
285
  static VALUE rb_eParseError;
47
286
  static VALUE rb_eScriptRuntimeError;
48
287
  static VALUE rb_cJavaScriptFunction;
288
+ static VALUE rb_eSnapshotError;
289
+ static VALUE rb_ePlatformAlreadyInitializedError;
290
+ static VALUE rb_mJSON;
291
+
292
+ static VALUE rb_cFailedV8Conversion;
293
+ static VALUE rb_cDateTime = Qnil;
294
+
295
+ static std::unique_ptr<Platform> current_platform = NULL;
296
+ static std::mutex platform_lock;
297
+
298
+ static pthread_attr_t *thread_attr_p;
299
+ static pthread_rwlock_t exit_lock = PTHREAD_RWLOCK_INITIALIZER;
300
+ static bool ruby_exiting = false; // guarded by exit_lock
301
+ static bool single_threaded = false;
302
+
303
+ static void mark_context(void *);
304
+ static void deallocate(void *);
305
+ static size_t context_memsize(const void *);
306
+ static const rb_data_type_t context_type = {
307
+ "mini_racer/context_info",
308
+ { mark_context, deallocate, context_memsize }
309
+ };
310
+
311
+ static void deallocate_snapshot(void *);
312
+ static size_t snapshot_memsize(const void *);
313
+ static const rb_data_type_t snapshot_type = {
314
+ "mini_racer/snapshot_info",
315
+ { NULL, deallocate_snapshot, snapshot_memsize }
316
+ };
317
+
318
+ static void mark_isolate(void *);
319
+ static void deallocate_isolate(void *);
320
+ static size_t isolate_memsize(const void *);
321
+ static const rb_data_type_t isolate_type = {
322
+ "mini_racer/isolate_info",
323
+ { mark_isolate, deallocate_isolate, isolate_memsize }
324
+ };
325
+
326
+ static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
327
+ bool platform_already_initialized = false;
328
+
329
+ if(TYPE(flag_as_str) != T_STRING) {
330
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE" (should be a string)",
331
+ rb_obj_class(flag_as_str));
332
+ }
333
+
334
+ platform_lock.lock();
335
+
336
+ if (current_platform == NULL) {
337
+ if (!strcmp(RSTRING_PTR(flag_as_str), "--single_threaded")) {
338
+ single_threaded = true;
339
+ }
340
+ V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
341
+ } else {
342
+ platform_already_initialized = true;
343
+ }
49
344
 
50
- static Platform* current_platform = NULL;
345
+ platform_lock.unlock();
346
+
347
+ // important to raise outside of the lock
348
+ if (platform_already_initialized) {
349
+ rb_raise(rb_ePlatformAlreadyInitializedError, "The V8 platform is already initialized");
350
+ }
351
+
352
+ return Qnil;
353
+ }
51
354
 
52
355
  static void init_v8() {
356
+ // no need to wait for the lock if already initialized
357
+ if (current_platform != NULL) return;
358
+
359
+ platform_lock.lock();
360
+
53
361
  if (current_platform == NULL) {
54
- V8::InitializeICU();
55
- current_platform = platform::CreateDefaultPlatform();
56
- V8::InitializePlatform(current_platform);
57
- V8::Initialize();
362
+ V8::InitializeICU();
363
+ current_platform = platform::NewDefaultPlatform();
364
+ V8::InitializePlatform(current_platform.get());
365
+ V8::Initialize();
366
+ }
367
+
368
+ platform_lock.unlock();
369
+ }
370
+
371
+ static void gc_callback(Isolate *isolate, GCType type, GCCallbackFlags flags) {
372
+ if (IsolateData::Get(isolate, IsolateData::MEM_SOFTLIMIT_REACHED)) {
373
+ return;
374
+ }
375
+
376
+ size_t softlimit = IsolateData::Get(isolate, IsolateData::MEM_SOFTLIMIT_MAX);
377
+
378
+ HeapStatistics stats;
379
+ isolate->GetHeapStatistics(&stats);
380
+ size_t used = stats.used_heap_size();
381
+
382
+ if(used > softlimit) {
383
+ IsolateData::Set(isolate, IsolateData::MEM_SOFTLIMIT_REACHED, true);
384
+ isolate->TerminateExecution();
58
385
  }
59
386
  }
60
387
 
61
- void* breaker(void *d) {
62
- EvalParams* data = (EvalParams*)d;
63
- usleep(data->timeout*1000);
64
- pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
65
- V8::TerminateExecution(data->context_info->isolate);
66
- return NULL;
388
+ // to be called with active lock and scope
389
+ static void prepare_result(MaybeLocal<Value> v8res,
390
+ TryCatch& trycatch,
391
+ Isolate* isolate,
392
+ Local<Context> context,
393
+ EvalResult& evalRes /* out */) {
394
+
395
+ // just don't touch .parsed
396
+ evalRes.terminated = false;
397
+ evalRes.json = false;
398
+ evalRes.value = nullptr;
399
+ evalRes.message = nullptr;
400
+ evalRes.backtrace = nullptr;
401
+ evalRes.executed = !v8res.IsEmpty();
402
+
403
+ if (evalRes.executed) {
404
+ // arrays and objects get converted to json
405
+ Local<Value> local_value = v8res.ToLocalChecked();
406
+ if ((local_value->IsObject() || local_value->IsArray()) &&
407
+ !local_value->IsDate() && !local_value->IsFunction()) {
408
+ Local<Object> JSON = context->Global()->Get(
409
+ context, String::NewFromUtf8Literal(isolate, "JSON"))
410
+ .ToLocalChecked().As<Object>();
411
+
412
+ Local<Function> stringify = JSON->Get(
413
+ context, v8::String::NewFromUtf8Literal(isolate, "stringify"))
414
+ .ToLocalChecked().As<Function>();
415
+
416
+ Local<Object> object = local_value->ToObject(context).ToLocalChecked();
417
+ const unsigned argc = 1;
418
+ Local<Value> argv[argc] = { object };
419
+ MaybeLocal<Value> json = stringify->Call(context, JSON, argc, argv);
420
+
421
+ if (json.IsEmpty()) {
422
+ evalRes.executed = false;
423
+ } else {
424
+ evalRes.json = true;
425
+ Persistent<Value>* persistent = new Persistent<Value>();
426
+ persistent->Reset(isolate, json.ToLocalChecked());
427
+ evalRes.value = persistent;
428
+ }
429
+
430
+ } else {
431
+ Persistent<Value>* persistent = new Persistent<Value>();
432
+ persistent->Reset(isolate, local_value);
433
+ evalRes.value = persistent;
434
+ }
435
+ }
436
+
437
+ if (!evalRes.executed || !evalRes.parsed) {
438
+ if (trycatch.HasCaught()) {
439
+ if (!trycatch.Exception()->IsNull()) {
440
+ evalRes.message = new Persistent<Value>();
441
+ Local<Message> message = trycatch.Message();
442
+ char buf[1000];
443
+ int len, line, column;
444
+
445
+ if (!message->GetLineNumber(context).To(&line)) {
446
+ line = 0;
447
+ }
448
+
449
+ if (!message->GetStartColumn(context).To(&column)) {
450
+ column = 0;
451
+ }
452
+
453
+ len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(isolate, message->Get()),
454
+ *String::Utf8Value(isolate, message->GetScriptResourceName()->ToString(context).ToLocalChecked()),
455
+ line,
456
+ column);
457
+
458
+ if ((size_t) len >= sizeof(buf)) {
459
+ len = sizeof(buf) - 1;
460
+ buf[len] = '\0';
461
+ }
462
+
463
+ Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, len).ToLocalChecked();
464
+ evalRes.message->Reset(isolate, v8_message);
465
+ } else if(trycatch.HasTerminated()) {
466
+ evalRes.terminated = true;
467
+ evalRes.message = new Persistent<Value>();
468
+ Local<String> tmp = String::NewFromUtf8Literal(isolate, "JavaScript was terminated (either by timeout or explicitly)");
469
+ evalRes.message->Reset(isolate, tmp);
470
+ }
471
+ if (!trycatch.StackTrace(context).IsEmpty()) {
472
+ evalRes.backtrace = new Persistent<Value>();
473
+ evalRes.backtrace->Reset(isolate,
474
+ trycatch.StackTrace(context).ToLocalChecked()->ToString(context).ToLocalChecked());
475
+ }
476
+ }
477
+ }
67
478
  }
68
479
 
69
- void*
480
+ static void*
70
481
  nogvl_context_eval(void* arg) {
482
+
71
483
  EvalParams* eval_params = (EvalParams*)arg;
72
484
  EvalResult* result = eval_params->result;
73
- Isolate* isolate = eval_params->context_info->isolate;
485
+ IsolateInfo* isolate_info = eval_params->context_info->isolate_info;
486
+ Isolate* isolate = isolate_info->isolate;
487
+
74
488
  Isolate::Scope isolate_scope(isolate);
75
489
  HandleScope handle_scope(isolate);
76
-
77
490
  TryCatch trycatch(isolate);
78
-
79
491
  Local<Context> context = eval_params->context_info->context->Get(isolate);
80
-
81
492
  Context::Scope context_scope(context);
493
+ v8::ScriptOrigin *origin = NULL;
494
+
495
+ IsolateData::Init(isolate);
496
+
497
+ if (eval_params->max_memory > 0) {
498
+ IsolateData::Set(isolate, IsolateData::MEM_SOFTLIMIT_MAX, eval_params->max_memory);
499
+ if (!isolate_info->added_gc_cb) {
500
+ isolate->AddGCEpilogueCallback(gc_callback);
501
+ isolate_info->added_gc_cb = true;
502
+ }
503
+ }
504
+
505
+ MaybeLocal<Script> parsed_script;
506
+
507
+ if (eval_params->filename) {
508
+ origin = new v8::ScriptOrigin(*eval_params->filename);
509
+ }
510
+
511
+ parsed_script = Script::Compile(context, *eval_params->eval, origin);
512
+
513
+ if (origin) {
514
+ delete origin;
515
+ }
82
516
 
83
- MaybeLocal<Script> parsed_script = Script::Compile(context, *eval_params->eval);
84
517
  result->parsed = !parsed_script.IsEmpty();
85
518
  result->executed = false;
86
519
  result->terminated = false;
520
+ result->json = false;
87
521
  result->value = NULL;
88
522
 
523
+ MaybeLocal<Value> maybe_value;
89
524
  if (!result->parsed) {
90
- result->message = new Persistent<Value>();
91
- result->message->Reset(isolate, trycatch.Exception());
525
+ result->message = new Persistent<Value>();
526
+ result->message->Reset(isolate, trycatch.Exception());
92
527
  } else {
528
+ // parsing successful
529
+ if (eval_params->marshal_stackdepth > 0) {
530
+ StackCounter::SetMax(isolate, eval_params->marshal_stackdepth);
531
+ }
93
532
 
94
- pthread_t breaker_thread;
95
-
96
- if (eval_params->timeout > 0) {
97
- pthread_create(&breaker_thread, NULL, breaker, (void*)eval_params);
98
- }
99
-
100
- MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
533
+ maybe_value = parsed_script.ToLocalChecked()->Run(context);
534
+ }
101
535
 
102
- if (eval_params->timeout > 0) {
103
- pthread_cancel(breaker_thread);
104
- pthread_join(breaker_thread, NULL);
105
- }
536
+ prepare_result(maybe_value, trycatch, isolate, context, *result);
106
537
 
107
- result->executed = !maybe_value.IsEmpty();
108
-
109
- if (!result->executed) {
110
- if (trycatch.HasCaught()) {
111
- if (!trycatch.Exception()->IsNull()) {
112
- result->message = new Persistent<Value>();
113
- result->message->Reset(isolate, trycatch.Exception()->ToString());
114
- } else if(trycatch.HasTerminated()) {
115
- result->terminated = true;
116
- result->message = new Persistent<Value>();
117
- Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
118
- result->message->Reset(isolate, tmp);
119
- }
120
-
121
- if (!trycatch.StackTrace().IsEmpty()) {
122
- result->backtrace = new Persistent<Value>();
123
- result->backtrace->Reset(isolate, trycatch.StackTrace()->ToString());
124
- }
125
- }
126
- } else {
127
- Persistent<Value>* persistent = new Persistent<Value>();
128
- persistent->Reset(isolate, maybe_value.ToLocalChecked());
129
- result->value = persistent;
130
- }
131
- }
538
+ IsolateData::Set(isolate, IsolateData::IN_GVL, true);
132
539
 
133
540
  return NULL;
134
541
  }
135
542
 
136
- static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
543
+ static VALUE new_empty_failed_conv_obj() {
544
+ // TODO isolate code that translates execption to ruby
545
+ // exception so we can properly return it
546
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
547
+ }
548
+
549
+ // assumes isolate locking is in place
550
+ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
551
+ Local<Value> value) {
137
552
 
553
+ Isolate::Scope isolate_scope(isolate);
138
554
  HandleScope scope(isolate);
139
555
 
556
+ StackCounter stackCounter(isolate);
557
+
558
+ if (IsolateData::Get(isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED)) {
559
+ return Qnil;
560
+ }
561
+
562
+ if (stackCounter.IsTooDeep()) {
563
+ IsolateData::Set(isolate, IsolateData::DO_TERMINATE, true);
564
+ isolate->TerminateExecution();
565
+ return Qnil;
566
+ }
567
+
140
568
  if (value->IsNull() || value->IsUndefined()){
141
- return Qnil;
569
+ return Qnil;
142
570
  }
143
571
 
144
572
  if (value->IsInt32()) {
145
- return INT2FIX(value->Int32Value());
573
+ return INT2FIX(value->Int32Value(context).ToChecked());
146
574
  }
147
575
 
148
576
  if (value->IsNumber()) {
149
- return rb_float_new(value->NumberValue());
577
+ return rb_float_new(value->NumberValue(context).ToChecked());
150
578
  }
151
579
 
152
580
  if (value->IsTrue()) {
153
- return Qtrue;
581
+ return Qtrue;
154
582
  }
155
583
 
156
584
  if (value->IsFalse()) {
157
- return Qfalse;
585
+ return Qfalse;
158
586
  }
159
587
 
160
588
  if (value->IsArray()) {
161
589
  VALUE rb_array = rb_ary_new();
162
590
  Local<Array> arr = Local<Array>::Cast(value);
163
591
  for(uint32_t i=0; i < arr->Length(); i++) {
164
- Local<Value> element = arr->Get(i);
165
- VALUE rb_elem = convert_v8_to_ruby(isolate, element);
592
+ MaybeLocal<Value> element = arr->Get(context, i);
593
+ if (element.IsEmpty()) {
594
+ continue;
595
+ }
596
+ VALUE rb_elem = convert_v8_to_ruby(isolate, context, element.ToLocalChecked());
597
+ if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
598
+ return rb_elem;
599
+ }
166
600
  rb_ary_push(rb_array, rb_elem);
167
601
  }
168
602
  return rb_array;
169
603
  }
170
604
 
171
605
  if (value->IsFunction()){
172
- return rb_funcall(rb_cJavaScriptFunction, rb_intern("new"), 0);
606
+ return rb_funcall(rb_cJavaScriptFunction, rb_intern("new"), 0);
607
+ }
608
+
609
+ if (value->IsDate()){
610
+ double ts = Local<Date>::Cast(value)->ValueOf();
611
+ double secs = ts/1000;
612
+ long nanos = round((secs - floor(secs)) * 1000000);
613
+
614
+ return rb_time_new(secs, nanos);
173
615
  }
174
616
 
175
617
  if (value->IsObject()) {
176
- VALUE rb_hash = rb_hash_new();
177
- Local<Context> context = Context::New(isolate);
178
- Local<Object> object = value->ToObject();
179
- MaybeLocal<Array> maybe_props = object->GetOwnPropertyNames(context);
180
- if (!maybe_props.IsEmpty()) {
181
- Local<Array> props = maybe_props.ToLocalChecked();
182
- for(uint32_t i=0; i < props->Length(); i++) {
183
- Local<Value> key = props->Get(i);
184
- VALUE rb_key = convert_v8_to_ruby(isolate, key);
185
- Local<Value> value = object->Get(key);
186
- VALUE rb_value = convert_v8_to_ruby(isolate, value);
187
- rb_hash_aset(rb_hash, rb_key, rb_value);
188
- }
189
- }
190
- return rb_hash;
618
+ VALUE rb_hash = rb_hash_new();
619
+ TryCatch trycatch(isolate);
620
+
621
+ Local<Object> object = value->ToObject(context).ToLocalChecked();
622
+ auto maybe_props = object->GetOwnPropertyNames(context);
623
+ if (!maybe_props.IsEmpty()) {
624
+ Local<Array> props = maybe_props.ToLocalChecked();
625
+ for(uint32_t i=0; i < props->Length(); i++) {
626
+ MaybeLocal<Value> key = props->Get(context, i);
627
+ if (key.IsEmpty()) {
628
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
629
+ }
630
+ VALUE rb_key = convert_v8_to_ruby(isolate, context, key.ToLocalChecked());
631
+
632
+ MaybeLocal<Value> prop_value = object->Get(context, key.ToLocalChecked());
633
+ // this may have failed due to Get raising
634
+ if (prop_value.IsEmpty() || trycatch.HasCaught()) {
635
+ return new_empty_failed_conv_obj();
636
+ }
637
+
638
+ VALUE rb_value = convert_v8_to_ruby(
639
+ isolate, context, prop_value.ToLocalChecked());
640
+ rb_hash_aset(rb_hash, rb_key, rb_value);
641
+ }
642
+ }
643
+ return rb_hash;
191
644
  }
192
645
 
193
- Local<String> rstr = value->ToString();
194
- return rb_enc_str_new(*v8::String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
646
+ if (value->IsSymbol()) {
647
+ v8::String::Utf8Value symbol_name(isolate,
648
+ Local<Symbol>::Cast(value)->Name());
649
+
650
+ VALUE str_symbol = rb_enc_str_new(
651
+ *symbol_name,
652
+ symbol_name.length(),
653
+ rb_enc_find("utf-8")
654
+ );
655
+
656
+ return rb_str_intern(str_symbol);
657
+ }
658
+
659
+ MaybeLocal<String> rstr_maybe = value->ToString(context);
660
+
661
+ if (rstr_maybe.IsEmpty()) {
662
+ return Qnil;
663
+ } else {
664
+ Local<String> rstr = rstr_maybe.ToLocalChecked();
665
+ return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
666
+ }
667
+ }
668
+
669
+ static VALUE convert_v8_to_ruby(Isolate* isolate,
670
+ const Persistent<Context>& context,
671
+ Local<Value> value) {
672
+ HandleScope scope(isolate);
673
+ return convert_v8_to_ruby(isolate,
674
+ Local<Context>::New(isolate, context),
675
+ value);
676
+ }
677
+
678
+ static VALUE convert_v8_to_ruby(Isolate* isolate,
679
+ const Persistent<Context>& context,
680
+ const Persistent<Value>& value) {
681
+ HandleScope scope(isolate);
682
+ return convert_v8_to_ruby(isolate,
683
+ Local<Context>::New(isolate, context),
684
+ Local<Value>::New(isolate, value));
195
685
  }
196
686
 
197
- static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
687
+ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context, VALUE value) {
198
688
  EscapableHandleScope scope(isolate);
199
689
 
690
+ Local<Array> array;
691
+ Local<Object> object;
692
+ VALUE hash_as_array;
693
+ VALUE pair;
694
+ int i;
695
+ long length;
696
+ long fixnum;
697
+ VALUE klass;
698
+
200
699
  switch (TYPE(value)) {
201
700
  case T_FIXNUM:
202
- return scope.Escape(Integer::New(isolate, NUM2INT(value)));
701
+ fixnum = NUM2LONG(value);
702
+ if (fixnum > INT_MAX)
703
+ {
704
+ return scope.Escape(Number::New(isolate, (double)fixnum));
705
+ }
706
+ return scope.Escape(Integer::New(isolate, (int)fixnum));
203
707
  case T_FLOAT:
204
708
  return scope.Escape(Number::New(isolate, NUM2DBL(value)));
205
709
  case T_STRING:
@@ -210,144 +714,496 @@ static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
210
714
  return scope.Escape(True(isolate));
211
715
  case T_FALSE:
212
716
  return scope.Escape(False(isolate));
717
+ case T_ARRAY:
718
+ length = RARRAY_LEN(value);
719
+ array = Array::New(isolate, (int)length);
720
+ for(i=0; i<length; i++) {
721
+ Maybe<bool> success = array->Set(context, i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
722
+ (void)(success);
723
+ }
724
+ return scope.Escape(array);
725
+ case T_HASH:
726
+ object = Object::New(isolate);
727
+ hash_as_array = rb_funcall(value, rb_intern("to_a"), 0);
728
+ length = RARRAY_LEN(hash_as_array);
729
+ for(i=0; i<length; i++) {
730
+ pair = rb_ary_entry(hash_as_array, i);
731
+ Maybe<bool> success = object->Set(context, convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
732
+ convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 1)));
733
+ (void)(success);
734
+ }
735
+ return scope.Escape(object);
736
+ case T_SYMBOL:
737
+ value = rb_funcall(value, rb_intern("to_s"), 0);
738
+ return scope.Escape(String::NewFromUtf8(isolate, RSTRING_PTR(value), NewStringType::kNormal, (int)RSTRING_LEN(value)).ToLocalChecked());
213
739
  case T_DATA:
740
+ klass = rb_funcall(value, rb_intern("class"), 0);
741
+ if (klass == rb_cTime || klass == rb_cDateTime)
742
+ {
743
+ if (klass == rb_cDateTime)
744
+ {
745
+ value = rb_funcall(value, rb_intern("to_time"), 0);
746
+ }
747
+ value = rb_funcall(value, rb_intern("to_f"), 0);
748
+ return scope.Escape(Date::New(context, NUM2DBL(value) * 1000).ToLocalChecked());
749
+ }
214
750
  case T_OBJECT:
215
751
  case T_CLASS:
216
752
  case T_ICLASS:
217
753
  case T_MODULE:
218
754
  case T_REGEXP:
219
755
  case T_MATCH:
220
- case T_ARRAY:
221
- case T_HASH:
222
756
  case T_STRUCT:
223
757
  case T_BIGNUM:
224
758
  case T_FILE:
225
- case T_SYMBOL:
226
759
  case T_UNDEF:
227
760
  case T_NODE:
228
761
  default:
229
- // rb_warn("unknown conversion to V8 for: %s", RSTRING_PTR(rb_inspect(value)));
230
- return scope.Escape(String::NewFromUtf8(isolate, "Undefined Conversion"));
762
+ return scope.Escape(String::NewFromUtf8Literal(isolate, "Undefined Conversion"));
763
+ }
231
764
  }
232
-
233
- }
234
765
 
235
766
  static void unblock_eval(void *ptr) {
236
767
  EvalParams* eval = (EvalParams*)ptr;
237
- eval->context_info->interrupted = true;
768
+ eval->context_info->isolate_info->interrupted = true;
238
769
  }
239
770
 
771
+ /*
772
+ * The implementations of the run_extra_code(), create_snapshot_data_blob() and
773
+ * warm_up_snapshot_data_blob() functions have been derived from V8's test suite.
774
+ */
775
+ static bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
776
+ const char *utf8_source, const char *name) {
777
+ Context::Scope context_scope(context);
778
+ TryCatch try_catch(isolate);
779
+ Local<String> source_string;
780
+ if (!String::NewFromUtf8(isolate, utf8_source).ToLocal(&source_string)) {
781
+ return false;
782
+ }
783
+ Local<String> resource_name =
784
+ String::NewFromUtf8(isolate, name).ToLocalChecked();
785
+ ScriptOrigin origin(resource_name);
786
+ ScriptCompiler::Source source(source_string, origin);
787
+ Local<Script> script;
788
+ if (!ScriptCompiler::Compile(context, &source).ToLocal(&script))
789
+ return false;
790
+ if (script->Run(context).IsEmpty()) return false;
791
+ return true;
792
+ }
240
793
 
241
- static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
794
+ static StartupData
795
+ create_snapshot_data_blob(const char *embedded_source = nullptr) {
796
+ Isolate *isolate = Isolate::Allocate();
797
+
798
+ // Optionally run a script to embed, and serialize to create a snapshot blob.
799
+ SnapshotCreator snapshot_creator(isolate);
800
+ {
801
+ HandleScope scope(isolate);
802
+ Local<v8::Context> context = v8::Context::New(isolate);
803
+ if (embedded_source != nullptr &&
804
+ !run_extra_code(isolate, context, embedded_source, "<embedded>")) {
805
+ return {};
806
+ }
807
+ snapshot_creator.SetDefaultContext(context);
808
+ }
809
+ return snapshot_creator.CreateBlob(
810
+ SnapshotCreator::FunctionCodeHandling::kClear);
811
+ }
242
812
 
243
- EvalParams eval_params;
244
- EvalResult eval_result;
813
+ static
814
+ StartupData warm_up_snapshot_data_blob(StartupData cold_snapshot_blob,
815
+ const char *warmup_source) {
816
+ // Use following steps to create a warmed up snapshot blob from a cold one:
817
+ // - Create a new isolate from the cold snapshot.
818
+ // - Create a new context to run the warmup script. This will trigger
819
+ // compilation of executed functions.
820
+ // - Create a new context. This context will be unpolluted.
821
+ // - Serialize the isolate and the second context into a new snapshot blob.
822
+ StartupData result = {nullptr, 0};
823
+
824
+ if (cold_snapshot_blob.raw_size > 0 && cold_snapshot_blob.data != nullptr &&
825
+ warmup_source != NULL) {
826
+ SnapshotCreator snapshot_creator(nullptr, &cold_snapshot_blob);
827
+ Isolate *isolate = snapshot_creator.GetIsolate();
828
+ {
829
+ HandleScope scope(isolate);
830
+ Local<Context> context = Context::New(isolate);
831
+ if (!run_extra_code(isolate, context, warmup_source, "<warm-up>")) {
832
+ return result;
833
+ }
834
+ }
835
+ {
836
+ HandleScope handle_scope(isolate);
837
+ isolate->ContextDisposedNotification(false);
838
+ Local<Context> context = Context::New(isolate);
839
+ snapshot_creator.SetDefaultContext(context);
840
+ }
841
+ result = snapshot_creator.CreateBlob(
842
+ SnapshotCreator::FunctionCodeHandling::kKeep);
843
+ }
844
+ return result;
845
+ }
846
+
847
+ static VALUE rb_snapshot_size(VALUE self, VALUE str) {
848
+ SnapshotInfo* snapshot_info;
849
+ TypedData_Get_Struct(self, SnapshotInfo, &snapshot_type, snapshot_info);
850
+
851
+ return INT2NUM(snapshot_info->raw_size);
852
+ }
853
+
854
+ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
855
+ SnapshotInfo* snapshot_info;
856
+ TypedData_Get_Struct(self, SnapshotInfo, &snapshot_type, snapshot_info);
857
+
858
+ if(TYPE(str) != T_STRING) {
859
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
860
+ rb_obj_class(str));
861
+ }
862
+
863
+ init_v8();
864
+
865
+ StartupData startup_data = create_snapshot_data_blob(RSTRING_PTR(str));
866
+
867
+ if (startup_data.data == NULL && startup_data.raw_size == 0) {
868
+ rb_raise(rb_eSnapshotError, "Could not create snapshot, most likely the source is incorrect");
869
+ }
870
+
871
+ snapshot_info->data = startup_data.data;
872
+ snapshot_info->raw_size = startup_data.raw_size;
873
+
874
+ return Qnil;
875
+ }
876
+
877
+ static VALUE rb_snapshot_dump(VALUE self, VALUE str) {
878
+ SnapshotInfo* snapshot_info;
879
+ TypedData_Get_Struct(self, SnapshotInfo, &snapshot_type, snapshot_info);
880
+
881
+ return rb_str_new(snapshot_info->data, snapshot_info->raw_size);
882
+ }
883
+
884
+ static VALUE rb_snapshot_warmup_unsafe(VALUE self, VALUE str) {
885
+ SnapshotInfo* snapshot_info;
886
+ TypedData_Get_Struct(self, SnapshotInfo, &snapshot_type, snapshot_info);
887
+
888
+ if(TYPE(str) != T_STRING) {
889
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
890
+ rb_obj_class(str));
891
+ }
892
+
893
+ init_v8();
894
+
895
+ StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
896
+ StartupData warm_startup_data = warm_up_snapshot_data_blob(cold_startup_data, RSTRING_PTR(str));
897
+
898
+ if (warm_startup_data.data == NULL && warm_startup_data.raw_size == 0) {
899
+ rb_raise(rb_eSnapshotError, "Could not warm up snapshot, most likely the source is incorrect");
900
+ } else {
901
+ delete[] snapshot_info->data;
902
+
903
+ snapshot_info->data = warm_startup_data.data;
904
+ snapshot_info->raw_size = warm_startup_data.raw_size;
905
+ }
906
+
907
+ return self;
908
+ }
909
+
910
+ void IsolateInfo::init(SnapshotInfo* snapshot_info) {
911
+ allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
912
+
913
+ Isolate::CreateParams create_params;
914
+ create_params.array_buffer_allocator = allocator;
915
+
916
+ if (snapshot_info) {
917
+ int raw_size = snapshot_info->raw_size;
918
+ char* data = new char[raw_size];
919
+ memcpy(data, snapshot_info->data, raw_size);
920
+
921
+ startup_data = new StartupData;
922
+ startup_data->data = data;
923
+ startup_data->raw_size = raw_size;
924
+
925
+ create_params.snapshot_blob = startup_data;
926
+ }
927
+
928
+ isolate = Isolate::New(create_params);
929
+ }
930
+
931
+ static VALUE rb_isolate_init_with_snapshot(VALUE self, VALUE snapshot) {
932
+ IsolateInfo* isolate_info;
933
+ TypedData_Get_Struct(self, IsolateInfo, &isolate_type, isolate_info);
934
+
935
+ init_v8();
936
+
937
+ SnapshotInfo* snapshot_info = nullptr;
938
+ if (!NIL_P(snapshot)) {
939
+ TypedData_Get_Struct(snapshot, SnapshotInfo, &snapshot_type, snapshot_info);
940
+ }
941
+
942
+ isolate_info->init(snapshot_info);
943
+ isolate_info->hold();
944
+
945
+ return Qnil;
946
+ }
947
+
948
+ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
949
+ IsolateInfo* isolate_info;
950
+ TypedData_Get_Struct(self, IsolateInfo, &isolate_type, isolate_info);
951
+
952
+ if (current_platform == NULL) return Qfalse;
953
+
954
+ double duration = NUM2DBL(idle_time_in_ms) / 1000.0;
955
+ double now = current_platform->MonotonicallyIncreasingTime();
956
+ return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
957
+ }
958
+
959
+ static VALUE rb_isolate_low_memory_notification(VALUE self) {
960
+ IsolateInfo* isolate_info;
961
+ TypedData_Get_Struct(self, IsolateInfo, &isolate_type, isolate_info);
962
+
963
+ if (current_platform == NULL) return Qfalse;
964
+
965
+ isolate_info->isolate->LowMemoryNotification();
966
+ return Qnil;
967
+ }
968
+
969
+ static VALUE rb_isolate_pump_message_loop(VALUE self) {
970
+ IsolateInfo* isolate_info;
971
+ TypedData_Get_Struct(self, IsolateInfo, &isolate_type, isolate_info);
972
+
973
+ if (current_platform == NULL) return Qfalse;
974
+
975
+ if (platform::PumpMessageLoop(current_platform.get(), isolate_info->isolate)){
976
+ return Qtrue;
977
+ } else {
978
+ return Qfalse;
979
+ }
980
+ }
981
+
982
+ static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
245
983
  ContextInfo* context_info;
246
- VALUE result;
984
+ TypedData_Get_Struct(self, ContextInfo, &context_type, context_info);
247
985
 
248
- VALUE message = Qnil;
249
- VALUE backtrace = Qnil;
986
+ init_v8();
987
+
988
+ IsolateInfo* isolate_info;
989
+
990
+ if (NIL_P(isolate) || !rb_obj_is_kind_of(isolate, rb_cIsolate)) {
991
+ isolate_info = new IsolateInfo();
250
992
 
251
- Data_Get_Struct(self, ContextInfo, context_info);
993
+ SnapshotInfo *snapshot_info = nullptr;
994
+ if (!NIL_P(snap) && rb_obj_is_kind_of(snap, rb_cSnapshot)) {
995
+ TypedData_Get_Struct(snap, SnapshotInfo, &snapshot_type, snapshot_info);
996
+ }
997
+ isolate_info->init(snapshot_info);
998
+ } else { // given isolate or snapshot
999
+ TypedData_Get_Struct(isolate, IsolateInfo, &isolate_type, isolate_info);
1000
+ }
1001
+
1002
+ context_info->isolate_info = isolate_info;
1003
+ isolate_info->hold();
252
1004
 
253
1005
  {
254
- Locker lock(context_info->isolate);
255
- Isolate::Scope isolate_scope(context_info->isolate);
256
- HandleScope handle_scope(context_info->isolate);
257
-
258
- Local<String> eval = String::NewFromUtf8(context_info->isolate, RSTRING_PTR(str),
259
- NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
260
-
261
- eval_params.context_info = context_info;
262
- eval_params.eval = &eval;
263
- eval_params.result = &eval_result;
264
- eval_params.timeout = 0;
265
- VALUE timeout = rb_iv_get(self, "@timeout");
266
- if (timeout != Qnil) {
267
- eval_params.timeout = (useconds_t)NUM2LONG(timeout);
268
- }
1006
+ // the ruby lock is needed if this isn't a new isolate
1007
+ IsolateInfo::Lock ruby_lock(isolate_info->mutex);
1008
+ Locker lock(isolate_info->isolate);
1009
+ Isolate::Scope isolate_scope(isolate_info->isolate);
1010
+ HandleScope handle_scope(isolate_info->isolate);
269
1011
 
270
- eval_result.message = NULL;
271
- eval_result.backtrace = NULL;
1012
+ Local<Context> context = Context::New(isolate_info->isolate);
272
1013
 
273
- rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
1014
+ context_info->context = new Persistent<Context>();
1015
+ context_info->context->Reset(isolate_info->isolate, context);
1016
+ }
274
1017
 
275
- if (eval_result.message != NULL) {
276
- Local<Value> tmp = Local<Value>::New(context_info->isolate, *eval_result.message);
277
- message = convert_v8_to_ruby(context_info->isolate, tmp);
278
- eval_result.message->Reset();
279
- delete eval_result.message;
280
- }
1018
+ if (Qnil == rb_cDateTime && rb_funcall(rb_cObject, rb_intern("const_defined?"), 1, rb_str_new2("DateTime")) == Qtrue)
1019
+ {
1020
+ rb_cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
1021
+ }
281
1022
 
282
- if (eval_result.backtrace != NULL) {
283
- Local<Value> tmp = Local<Value>::New(context_info->isolate, *eval_result.backtrace);
284
- backtrace = convert_v8_to_ruby(context_info->isolate, tmp);
285
- eval_result.backtrace->Reset();
286
- delete eval_result.backtrace;
287
- }
1023
+ return Qnil;
1024
+ }
1025
+
1026
+ static VALUE convert_result_to_ruby(VALUE self /* context */,
1027
+ EvalResult& result) {
1028
+ ContextInfo *context_info;
1029
+ TypedData_Get_Struct(self, ContextInfo, &context_type, context_info);
1030
+
1031
+ Isolate *isolate = context_info->isolate_info->isolate;
1032
+ Persistent<Context> *p_ctx = context_info->context;
1033
+
1034
+ VALUE message = Qnil;
1035
+ VALUE backtrace = Qnil;
1036
+ {
1037
+ Locker lock(isolate);
1038
+ if (result.message) {
1039
+ message = convert_v8_to_ruby(isolate, *p_ctx, *result.message);
1040
+ result.message->Reset();
1041
+ delete result.message;
1042
+ result.message = nullptr;
1043
+ }
1044
+
1045
+ if (result.backtrace) {
1046
+ backtrace = convert_v8_to_ruby(isolate, *p_ctx, *result.backtrace);
1047
+ result.backtrace->Reset();
1048
+ delete result.backtrace;
1049
+ }
288
1050
  }
289
1051
 
290
1052
  // NOTE: this is very important, we can not do an rb_raise from within
291
1053
  // a v8 scope, if we do the scope is never cleaned up properly and we leak
292
- if (!eval_result.parsed) {
293
- if(TYPE(message) == T_STRING) {
294
- rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
295
- } else {
296
- rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
297
- }
1054
+ if (!result.parsed) {
1055
+ if(TYPE(message) == T_STRING) {
1056
+ rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
1057
+ } else {
1058
+ rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
1059
+ }
298
1060
  }
299
1061
 
300
- if (!eval_result.executed) {
301
-
302
- VALUE ruby_exception = rb_iv_get(self, "@current_exception");
303
- if (ruby_exception == Qnil) {
304
- ruby_exception = eval_result.terminated ? rb_eScriptTerminatedError : rb_eScriptRuntimeError;
305
- // exception report about what happened
306
- if(TYPE(message) == T_STRING && TYPE(backtrace) == T_STRING) {
307
- rb_raise(ruby_exception, "%s/n%s", RSTRING_PTR(message), RSTRING_PTR(backtrace));
308
- } else if(TYPE(message) == T_STRING) {
309
- rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
310
- } else {
311
- rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
312
- }
313
- } else {
1062
+ if (!result.executed) {
1063
+ VALUE ruby_exception = rb_iv_get(self, "@current_exception");
1064
+ if (ruby_exception == Qnil) {
1065
+ bool mem_softlimit_reached = IsolateData::Get(isolate, IsolateData::MEM_SOFTLIMIT_REACHED);
1066
+ bool marshal_stack_maxdepth_reached = IsolateData::Get(isolate, IsolateData::MARSHAL_STACKDEPTH_REACHED);
1067
+ // If we were terminated or have the memory softlimit flag set
1068
+ if (marshal_stack_maxdepth_reached) {
1069
+ ruby_exception = rb_eScriptRuntimeError;
1070
+ std::string msg = std::string("Marshal object depth too deep. Script terminated.");
1071
+ message = rb_enc_str_new(msg.c_str(), msg.length(), rb_enc_find("utf-8"));
1072
+ } else if (result.terminated || mem_softlimit_reached) {
1073
+ ruby_exception = mem_softlimit_reached ? rb_eV8OutOfMemoryError : rb_eScriptTerminatedError;
1074
+ } else {
1075
+ ruby_exception = rb_eScriptRuntimeError;
1076
+ }
1077
+
1078
+ // exception report about what happened
1079
+ if (TYPE(backtrace) == T_STRING) {
1080
+ rb_raise(ruby_exception, "%s", RSTRING_PTR(backtrace));
1081
+ } else if(TYPE(message) == T_STRING) {
1082
+ rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
1083
+ } else {
1084
+ rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
1085
+ }
1086
+ } else {
314
1087
  VALUE rb_str = rb_funcall(ruby_exception, rb_intern("to_s"), 0);
315
- rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
316
- }
1088
+ rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
1089
+ }
317
1090
  }
318
1091
 
1092
+ VALUE ret = Qnil;
1093
+
319
1094
  // New scope for return value
320
1095
  {
321
- Locker lock(context_info->isolate);
322
- Isolate::Scope isolate_scope(context_info->isolate);
323
- HandleScope handle_scope(context_info->isolate);
1096
+ Locker lock(isolate);
1097
+ Isolate::Scope isolate_scope(isolate);
1098
+ HandleScope handle_scope(isolate);
1099
+
1100
+ Local<Value> tmp = Local<Value>::New(isolate, *result.value);
1101
+
1102
+ if (result.json) {
1103
+ Local<String> rstr = tmp->ToString(p_ctx->Get(isolate)).ToLocalChecked();
1104
+ VALUE json_string = rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
1105
+ ret = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
1106
+ } else {
1107
+ StackCounter::Reset(isolate);
1108
+ ret = convert_v8_to_ruby(isolate, *p_ctx, tmp);
1109
+ }
1110
+
1111
+ result.value->Reset();
1112
+ delete result.value;
1113
+ }
1114
+
1115
+ if (rb_funcall(ret, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
1116
+ // TODO try to recover stack trace from the conversion error
1117
+ rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
1118
+ }
1119
+
1120
+
1121
+ return ret;
1122
+ }
324
1123
 
325
- Local<Value> tmp = Local<Value>::New(context_info->isolate, *eval_result.value);
326
- result = convert_v8_to_ruby(context_info->isolate, tmp);
1124
+ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
327
1125
 
328
- eval_result.value->Reset();
329
- delete eval_result.value;
1126
+ EvalParams eval_params;
1127
+ EvalResult eval_result;
1128
+ ContextInfo* context_info;
1129
+
1130
+ TypedData_Get_Struct(self, ContextInfo, &context_type, context_info);
1131
+ Isolate* isolate = context_info->isolate_info->isolate;
1132
+
1133
+ if(TYPE(str) != T_STRING) {
1134
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
1135
+ rb_obj_class(str));
1136
+ }
1137
+ if(filename != Qnil && TYPE(filename) != T_STRING) {
1138
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be nil or a string)",
1139
+ rb_obj_class(filename));
330
1140
  }
331
1141
 
332
- return result;
1142
+ {
1143
+ Locker lock(isolate);
1144
+ Isolate::Scope isolate_scope(isolate);
1145
+ HandleScope handle_scope(isolate);
1146
+
1147
+ Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(str),
1148
+ NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
1149
+
1150
+ Local<String> local_filename;
1151
+
1152
+ if (filename != Qnil) {
1153
+ local_filename = String::NewFromUtf8(isolate, RSTRING_PTR(filename),
1154
+ NewStringType::kNormal, (int)RSTRING_LEN(filename)).ToLocalChecked();
1155
+ eval_params.filename = &local_filename;
1156
+ } else {
1157
+ eval_params.filename = NULL;
1158
+ }
1159
+
1160
+ eval_params.context_info = context_info;
1161
+ eval_params.eval = &eval;
1162
+ eval_params.result = &eval_result;
1163
+ eval_params.timeout = 0;
1164
+ eval_params.max_memory = 0;
1165
+ eval_params.marshal_stackdepth = 0;
1166
+ VALUE timeout = rb_iv_get(self, "@timeout");
1167
+ if (timeout != Qnil) {
1168
+ eval_params.timeout = (useconds_t)NUM2LONG(timeout);
1169
+ }
1170
+
1171
+ VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
1172
+ if (mem_softlimit != Qnil) {
1173
+ eval_params.max_memory = (size_t)NUM2ULONG(mem_softlimit);
1174
+ }
1175
+
1176
+ VALUE stack_depth = rb_iv_get(self, "@marshal_stack_depth");
1177
+ if (stack_depth != Qnil) {
1178
+ eval_params.marshal_stackdepth = (size_t)NUM2ULONG(stack_depth);
1179
+ }
1180
+
1181
+ eval_result.message = NULL;
1182
+ eval_result.backtrace = NULL;
1183
+
1184
+ rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
1185
+ }
1186
+
1187
+ return convert_result_to_ruby(self, eval_result);
333
1188
  }
334
1189
 
335
1190
  typedef struct {
336
1191
  VALUE callback;
337
1192
  int length;
338
- VALUE* args;
1193
+ VALUE ruby_args;
339
1194
  bool failed;
340
1195
  } protected_callback_data;
341
1196
 
342
- static
343
- VALUE protected_callback(VALUE rdata) {
1197
+ static VALUE protected_callback(VALUE rdata) {
344
1198
  protected_callback_data* data = (protected_callback_data*)rdata;
345
1199
  VALUE result;
346
1200
 
347
1201
  if (data->length > 0) {
348
- result = rb_funcall2(data->callback, rb_intern("call"), data->length, data->args);
1202
+ result = rb_funcall2(data->callback, rb_intern("call"), data->length,
1203
+ RARRAY_PTR(data->ruby_args));
1204
+ RB_GC_GUARD(data->ruby_args);
349
1205
  } else {
350
- result = rb_funcall(data->callback, rb_intern("call"), 0);
1206
+ result = rb_funcall(data->callback, rb_intern("call"), 0);
351
1207
  }
352
1208
  return result;
353
1209
  }
@@ -359,65 +1215,100 @@ VALUE rescue_callback(VALUE rdata, VALUE exception) {
359
1215
  return exception;
360
1216
  }
361
1217
 
362
- void*
1218
+ static void*
363
1219
  gvl_ruby_callback(void* data) {
364
1220
 
365
1221
  FunctionCallbackInfo<Value>* args = (FunctionCallbackInfo<Value>*)data;
366
- VALUE* ruby_args;
1222
+ VALUE ruby_args = Qnil;
367
1223
  int length = args->Length();
368
1224
  VALUE callback;
369
1225
  VALUE result;
370
1226
  VALUE self;
1227
+ VALUE parent;
1228
+ ContextInfo* context_info;
371
1229
 
372
1230
  {
373
- HandleScope scope(args->GetIsolate());
374
- Handle<External> external = Handle<External>::Cast(args->Data());
375
-
376
- VALUE* self_pointer = (VALUE*)(external->Value());
377
- self = *self_pointer;
378
- callback = rb_iv_get(self, "@callback");
379
-
380
- if (length > 0) {
381
- ruby_args = ALLOC_N(VALUE, length);
382
- }
383
-
384
-
385
- for (int i = 0; i < length; i++) {
386
- Local<Value> value = ((*args)[i]).As<Value>();
387
- ruby_args[i] = convert_v8_to_ruby(args->GetIsolate(), value);
388
- }
1231
+ HandleScope scope(args->GetIsolate());
1232
+ Local<External> external = Local<External>::Cast(args->Data());
1233
+
1234
+ self = (VALUE)(external->Value());
1235
+ callback = rb_iv_get(self, "@callback");
1236
+
1237
+ parent = rb_iv_get(self, "@parent");
1238
+ if (NIL_P(parent) || !rb_obj_is_kind_of(parent, rb_cContext)) {
1239
+ return NULL;
1240
+ }
1241
+
1242
+ TypedData_Get_Struct(parent, ContextInfo, &context_type, context_info);
1243
+
1244
+ if (length > 0) {
1245
+ ruby_args = rb_ary_tmp_new(length);
1246
+ }
1247
+
1248
+ for (int i = 0; i < length; i++) {
1249
+ Local<Value> value = ((*args)[i]).As<Value>();
1250
+ StackCounter::Reset(args->GetIsolate());
1251
+ VALUE tmp = convert_v8_to_ruby(args->GetIsolate(),
1252
+ *context_info->context, value);
1253
+ rb_ary_push(ruby_args, tmp);
1254
+ }
389
1255
  }
390
1256
 
391
1257
  // may raise exception stay clear of handle scope
392
1258
  protected_callback_data callback_data;
393
1259
  callback_data.length = length;
394
1260
  callback_data.callback = callback;
395
- callback_data.args = ruby_args;
1261
+ callback_data.ruby_args = ruby_args;
396
1262
  callback_data.failed = false;
397
1263
 
398
- result = rb_rescue((VALUE(*)(...))&protected_callback, (VALUE)(&callback_data),
399
- (VALUE(*)(...))&rescue_callback, (VALUE)(&callback_data));
1264
+ if (IsolateData::Get(args->GetIsolate(), IsolateData::DO_TERMINATE)) {
1265
+ args->GetIsolate()->ThrowException(
1266
+ String::NewFromUtf8Literal(args->GetIsolate(),
1267
+ "Terminated execution during transition from Ruby to JS"));
1268
+ args->GetIsolate()->TerminateExecution();
1269
+ if (length > 0) {
1270
+ rb_ary_clear(ruby_args);
1271
+ }
1272
+ return NULL;
1273
+ }
1274
+
1275
+ VALUE callback_data_value = (VALUE)&callback_data;
1276
+
1277
+ // TODO: use rb_vrescue2 in Ruby 2.7 and above
1278
+ result = rb_rescue2(protected_callback, callback_data_value,
1279
+ rescue_callback, callback_data_value, rb_eException, (VALUE)0);
400
1280
 
401
1281
  if(callback_data.failed) {
402
- VALUE parent = rb_iv_get(self, "@parent");
403
- rb_iv_set(parent, "@current_exception", result);
404
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
1282
+ rb_iv_set(parent, "@current_exception", result);
1283
+ args->GetIsolate()->ThrowException(String::NewFromUtf8Literal(args->GetIsolate(), "Ruby exception"));
405
1284
  }
406
1285
  else {
407
- HandleScope scope(args->GetIsolate());
408
- Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), result);
409
- args->GetReturnValue().Set(v8_result);
1286
+ HandleScope scope(args->GetIsolate());
1287
+ Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), context_info->context->Get(args->GetIsolate()), result);
1288
+ args->GetReturnValue().Set(v8_result);
410
1289
  }
411
1290
 
412
1291
  if (length > 0) {
413
- xfree(ruby_args);
1292
+ rb_ary_clear(ruby_args);
1293
+ }
1294
+
1295
+ if (IsolateData::Get(args->GetIsolate(), IsolateData::DO_TERMINATE)) {
1296
+ args->GetIsolate()->TerminateExecution();
414
1297
  }
415
1298
 
416
1299
  return NULL;
417
1300
  }
418
1301
 
419
1302
  static void ruby_callback(const FunctionCallbackInfo<Value>& args) {
420
- rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
1303
+ bool has_gvl = IsolateData::Get(args.GetIsolate(), IsolateData::IN_GVL);
1304
+
1305
+ if(has_gvl) {
1306
+ gvl_ruby_callback((void*)&args);
1307
+ } else {
1308
+ IsolateData::Set(args.GetIsolate(), IsolateData::IN_GVL, true);
1309
+ rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
1310
+ IsolateData::Set(args.GetIsolate(), IsolateData::IN_GVL, false);
1311
+ }
421
1312
  }
422
1313
 
423
1314
 
@@ -427,114 +1318,621 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
427
1318
 
428
1319
  VALUE parent = rb_iv_get(self, "@parent");
429
1320
  VALUE name = rb_iv_get(self, "@name");
1321
+ VALUE parent_object = rb_iv_get(self, "@parent_object");
1322
+ VALUE parent_object_eval = rb_iv_get(self, "@parent_object_eval");
430
1323
 
431
- Data_Get_Struct(parent, ContextInfo, context_info);
1324
+ bool parse_error = false;
1325
+ bool attach_error = false;
432
1326
 
433
- Locker lock(context_info->isolate);
434
- Isolate::Scope isolate_scope(context_info->isolate);
435
- HandleScope handle_scope(context_info->isolate);
1327
+ TypedData_Get_Struct(parent, ContextInfo, &context_type, context_info);
1328
+ Isolate* isolate = context_info->isolate_info->isolate;
436
1329
 
437
- Local<Context> context = context_info->context->Get(context_info->isolate);
438
- Context::Scope context_scope(context);
1330
+ {
1331
+ Locker lock(isolate);
1332
+ Isolate::Scope isolate_scope(isolate);
1333
+ HandleScope handle_scope(isolate);
1334
+
1335
+ Local<Context> context = context_info->context->Get(isolate);
1336
+ Context::Scope context_scope(context);
1337
+
1338
+ Local<String> v8_str =
1339
+ String::NewFromUtf8(isolate, RSTRING_PTR(name),
1340
+ NewStringType::kNormal, (int)RSTRING_LEN(name))
1341
+ .ToLocalChecked();
1342
+
1343
+ // Note that self (rb_cExternalFunction) is a pure Ruby T_OBJECT,
1344
+ // not a T_DATA type like most other classes in this file
1345
+ Local<Value> external = External::New(isolate, (void *)self);
1346
+
1347
+ if (parent_object == Qnil) {
1348
+ Maybe<bool> success = context->Global()->Set(
1349
+ context,
1350
+ v8_str,
1351
+ FunctionTemplate::New(isolate, ruby_callback, external)
1352
+ ->GetFunction(context)
1353
+ .ToLocalChecked());
1354
+ (void)success;
1355
+
1356
+ } else {
1357
+ Local<String> eval =
1358
+ String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
1359
+ NewStringType::kNormal,
1360
+ (int)RSTRING_LEN(parent_object_eval))
1361
+ .ToLocalChecked();
1362
+
1363
+ MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
1364
+ if (parsed_script.IsEmpty()) {
1365
+ parse_error = true;
1366
+ } else {
1367
+ MaybeLocal<Value> maybe_value =
1368
+ parsed_script.ToLocalChecked()->Run(context);
1369
+ attach_error = true;
1370
+
1371
+ if (!maybe_value.IsEmpty()) {
1372
+ Local<Value> value = maybe_value.ToLocalChecked();
1373
+ if (value->IsObject()) {
1374
+ Maybe<bool> success = value.As<Object>()->Set(
1375
+ context,
1376
+ v8_str,
1377
+ FunctionTemplate::New(isolate, ruby_callback, external)
1378
+ ->GetFunction(context)
1379
+ .ToLocalChecked());
1380
+ (void)success;
1381
+ attach_error = false;
1382
+ }
1383
+ }
1384
+ }
1385
+ }
1386
+ }
439
1387
 
440
- Local<String> v8_str = String::NewFromUtf8(context_info->isolate, RSTRING_PTR(name),
441
- NewStringType::kNormal, (int)RSTRING_LEN(name)).ToLocalChecked();
1388
+ // always raise out of V8 context
1389
+ if (parse_error) {
1390
+ rb_raise(rb_eParseError, "Invalid object %" PRIsVALUE, parent_object);
1391
+ }
442
1392
 
1393
+ if (attach_error) {
1394
+ rb_raise(rb_eParseError, "Was expecting %" PRIsVALUE" to be an object", parent_object);
1395
+ }
443
1396
 
444
- // copy self so we can access from v8 external
445
- VALUE* self_copy;
446
- Data_Get_Struct(self, VALUE, self_copy);
447
- *self_copy = self;
1397
+ return Qnil;
1398
+ }
448
1399
 
449
- Local<Value> external = External::New(context_info->isolate, self_copy);
450
- context->Global()->Set(v8_str, FunctionTemplate::New(context_info->isolate, ruby_callback, external)->GetFunction());
1400
+ static VALUE rb_context_isolate_mutex(VALUE self) {
1401
+ ContextInfo* context_info;
1402
+ TypedData_Get_Struct(self, ContextInfo, &context_type, context_info);
451
1403
 
452
- return Qnil;
1404
+ if (!context_info->isolate_info) {
1405
+ rb_raise(rb_eScriptRuntimeError, "Context has no Isolate available anymore");
1406
+ }
1407
+
1408
+ return context_info->isolate_info->mutex;
453
1409
  }
454
1410
 
455
- void deallocate(void * data) {
456
- ContextInfo* context_info = (ContextInfo*)data;
457
- {
458
- Locker lock(context_info->isolate);
1411
+ IsolateInfo::~IsolateInfo() {
1412
+ if (isolate) {
1413
+ if (this->interrupted) {
1414
+ fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, "
1415
+ "it can not be disposed and memory will not be "
1416
+ "reclaimed till the Ruby process exits.\n");
1417
+ } else {
1418
+ if (this->pid != getpid() && !single_threaded) {
1419
+ fprintf(stderr, "WARNING: V8 isolate was forked, "
1420
+ "it can not be disposed and "
1421
+ "memory will not be reclaimed "
1422
+ "till the Ruby process exits.\n"
1423
+ "It is VERY likely your process will hang.\n"
1424
+ "If you wish to use v8 in forked environment "
1425
+ "please ensure the platform is initialized with:\n"
1426
+ "MiniRacer::Platform.set_flags! :single_threaded\n"
1427
+ );
1428
+ } else {
1429
+ isolate->Dispose();
1430
+ }
1431
+ }
1432
+ isolate = nullptr;
459
1433
  }
460
1434
 
461
- {
462
- context_info->context->Reset();
463
- delete context_info->context;
1435
+ if (startup_data) {
1436
+ delete[] startup_data->data;
1437
+ delete startup_data;
464
1438
  }
465
1439
 
466
- {
467
- if (context_info->interrupted) {
468
- fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, it can not be disposed and memory will not be reclaimed till the Ruby process exits.");
469
- } else {
470
- context_info->isolate->Dispose();
471
- }
1440
+ delete allocator;
1441
+ }
1442
+
1443
+ static void free_context_raw(void *arg) {
1444
+ ContextInfo* context_info = (ContextInfo*)arg;
1445
+ IsolateInfo* isolate_info = context_info->isolate_info;
1446
+ Persistent<Context>* context = context_info->context;
1447
+
1448
+ if (context && isolate_info && isolate_info->isolate) {
1449
+ Locker lock(isolate_info->isolate);
1450
+ v8::Isolate::Scope isolate_scope(isolate_info->isolate);
1451
+ context->Reset();
1452
+ delete context;
1453
+ }
1454
+
1455
+ if (isolate_info) {
1456
+ isolate_info->release();
472
1457
  }
473
1458
 
474
- delete context_info->allocator;
475
1459
  xfree(context_info);
476
1460
  }
477
1461
 
478
- void deallocate_external_function(void * data) {
1462
+ static void *free_context_thr(void* arg) {
1463
+ if (pthread_rwlock_tryrdlock(&exit_lock) != 0) {
1464
+ return NULL;
1465
+ }
1466
+ if (ruby_exiting) {
1467
+ return NULL;
1468
+ }
1469
+
1470
+ free_context_raw(arg);
1471
+
1472
+ pthread_rwlock_unlock(&exit_lock);
1473
+
1474
+ return NULL;
1475
+ }
1476
+
1477
+ // destroys everything except freeing the ContextInfo struct (see deallocate())
1478
+ static void free_context(ContextInfo* context_info) {
1479
+
1480
+ IsolateInfo* isolate_info = context_info->isolate_info;
1481
+
1482
+ ContextInfo* context_info_copy = ALLOC(ContextInfo);
1483
+ context_info_copy->isolate_info = context_info->isolate_info;
1484
+ context_info_copy->context = context_info->context;
1485
+
1486
+ if (isolate_info && isolate_info->refs() > 1) {
1487
+ pthread_t free_context_thread;
1488
+ if (pthread_create(&free_context_thread, thread_attr_p,
1489
+ free_context_thr, (void*)context_info_copy)) {
1490
+ fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
1491
+ }
1492
+ } else {
1493
+ free_context_raw(context_info_copy);
1494
+ }
1495
+
1496
+ context_info->context = NULL;
1497
+ context_info->isolate_info = NULL;
1498
+ }
1499
+
1500
+ static void deallocate_isolate(void* data) {
1501
+
1502
+ IsolateInfo* isolate_info = (IsolateInfo*) data;
1503
+
1504
+ isolate_info->release();
1505
+ }
1506
+
1507
+ static void mark_isolate(void* data) {
1508
+ IsolateInfo* isolate_info = (IsolateInfo*) data;
1509
+ isolate_info->mark();
1510
+ }
1511
+
1512
+ static size_t isolate_memsize(const void *ptr) {
1513
+ const IsolateInfo *isolate_info = (const IsolateInfo *)ptr;
1514
+ return sizeof(*isolate_info);
1515
+ }
1516
+
1517
+ static void deallocate(void* data) {
1518
+ ContextInfo* context_info = (ContextInfo*)data;
1519
+
1520
+ free_context(context_info);
1521
+
479
1522
  xfree(data);
480
1523
  }
481
1524
 
482
- VALUE allocate_external_function(VALUE klass) {
483
- VALUE* self = ALLOC(VALUE);
484
- return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
1525
+ static size_t context_memsize(const void *ptr)
1526
+ {
1527
+ return sizeof(ContextInfo);
485
1528
  }
486
1529
 
487
- VALUE allocate(VALUE klass) {
488
- init_v8();
1530
+ static void mark_context(void* data) {
1531
+ ContextInfo* context_info = (ContextInfo*)data;
1532
+ if (context_info->isolate_info) {
1533
+ context_info->isolate_info->mark();
1534
+ }
1535
+ }
489
1536
 
490
- ContextInfo* context_info = ALLOC(ContextInfo);
491
- context_info->allocator = new ArrayBufferAllocator();
492
- context_info->interrupted = false;
493
- Isolate::CreateParams create_params;
494
- create_params.array_buffer_allocator = context_info->allocator;
1537
+ static void deallocate_snapshot(void * data) {
1538
+ SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
1539
+ delete[] snapshot_info->data;
1540
+ xfree(snapshot_info);
1541
+ }
495
1542
 
496
- context_info->isolate = Isolate::New(create_params);
1543
+ static size_t snapshot_memsize(const void *data) {
1544
+ SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
1545
+ return sizeof(*snapshot_info) + snapshot_info->raw_size;
1546
+ }
497
1547
 
498
- Locker lock(context_info->isolate);
499
- Isolate::Scope isolate_scope(context_info->isolate);
500
- HandleScope handle_scope(context_info->isolate);
1548
+ static VALUE allocate(VALUE klass) {
1549
+ ContextInfo* context_info;
1550
+ return TypedData_Make_Struct(klass, ContextInfo, &context_type, context_info);
1551
+ }
501
1552
 
502
- Local<Context> context = Context::New(context_info->isolate);
1553
+ static VALUE allocate_snapshot(VALUE klass) {
1554
+ SnapshotInfo* snapshot_info;
503
1555
 
504
- context_info->context = new Persistent<Context>();
505
- context_info->context->Reset(context_info->isolate, context);
1556
+ return TypedData_Make_Struct(klass, SnapshotInfo, &snapshot_type, snapshot_info);
1557
+ }
506
1558
 
507
- return Data_Wrap_Struct(klass, NULL, deallocate, (void*)context_info);
1559
+ static VALUE allocate_isolate(VALUE klass) {
1560
+ IsolateInfo* isolate_info = new IsolateInfo();
1561
+
1562
+ return TypedData_Wrap_Struct(klass, &isolate_type, isolate_info);
1563
+ }
1564
+
1565
+ static VALUE
1566
+ rb_heap_stats(VALUE self) {
1567
+
1568
+ ContextInfo* context_info;
1569
+ TypedData_Get_Struct(self, ContextInfo, &context_type, context_info);
1570
+ Isolate* isolate;
1571
+ v8::HeapStatistics stats;
1572
+
1573
+ isolate = context_info->isolate_info ? context_info->isolate_info->isolate : NULL;
1574
+
1575
+ VALUE rval = rb_hash_new();
1576
+
1577
+ if (!isolate) {
1578
+
1579
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(0));
1580
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(0));
1581
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(0));
1582
+ rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(0));
1583
+ rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(0));
1584
+
1585
+ } else {
1586
+ isolate->GetHeapStatistics(&stats);
1587
+
1588
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(stats.total_physical_size()));
1589
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(stats.total_heap_size_executable()));
1590
+ rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(stats.total_heap_size()));
1591
+ rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(stats.used_heap_size()));
1592
+ rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(stats.heap_size_limit()));
1593
+ }
1594
+
1595
+ return rval;
1596
+ }
1597
+
1598
+ // https://github.com/bnoordhuis/node-heapdump/blob/master/src/heapdump.cc
1599
+ class FileOutputStream : public OutputStream {
1600
+ public:
1601
+ int err;
1602
+
1603
+ FileOutputStream(int fd) : fd(fd) { err = 0; }
1604
+
1605
+ virtual int GetChunkSize() {
1606
+ return 65536;
1607
+ }
1608
+
1609
+ virtual void EndOfStream() {}
1610
+
1611
+ virtual WriteResult WriteAsciiChunk(char* data, int size) {
1612
+ size_t len = static_cast<size_t>(size);
1613
+
1614
+ while (len) {
1615
+ ssize_t w = write(fd, data, len);
1616
+
1617
+ if (w > 0) {
1618
+ data += w;
1619
+ len -= w;
1620
+ } else if (w < 0) {
1621
+ err = errno;
1622
+ return kAbort;
1623
+ } else { /* w == 0, could be out-of-space */
1624
+ err = -1;
1625
+ return kAbort;
1626
+ }
1627
+ }
1628
+ return kContinue;
1629
+ }
1630
+
1631
+ private:
1632
+ int fd;
1633
+ };
1634
+
1635
+
1636
+ static VALUE
1637
+ rb_heap_snapshot(VALUE self, VALUE file) {
1638
+
1639
+ rb_io_t *fptr;
1640
+
1641
+ fptr = RFILE(file)->fptr;
1642
+
1643
+ if (!fptr) return Qfalse;
1644
+
1645
+ // prepare for unbuffered write(2) below:
1646
+ rb_funcall(file, rb_intern("flush"), 0);
1647
+
1648
+ ContextInfo* context_info;
1649
+ TypedData_Get_Struct(self, ContextInfo, &context_type, context_info);
1650
+ Isolate* isolate;
1651
+ isolate = context_info->isolate_info ? context_info->isolate_info->isolate : NULL;
1652
+
1653
+ if (!isolate) return Qfalse;
1654
+
1655
+ Locker lock(isolate);
1656
+ Isolate::Scope isolate_scope(isolate);
1657
+ HandleScope handle_scope(isolate);
1658
+
1659
+ HeapProfiler* heap_profiler = isolate->GetHeapProfiler();
1660
+
1661
+ const HeapSnapshot* const snap = heap_profiler->TakeHeapSnapshot();
1662
+
1663
+ FileOutputStream stream(fptr->fd);
1664
+ snap->Serialize(&stream, HeapSnapshot::kJSON);
1665
+
1666
+ const_cast<HeapSnapshot*>(snap)->Delete();
1667
+
1668
+ /* TODO: perhaps rb_sys_fail here */
1669
+ if (stream.err) return Qfalse;
1670
+
1671
+ return Qtrue;
508
1672
  }
509
1673
 
510
1674
  static VALUE
511
1675
  rb_context_stop(VALUE self) {
1676
+
512
1677
  ContextInfo* context_info;
513
- Data_Get_Struct(self, ContextInfo, context_info);
514
- V8::TerminateExecution(context_info->isolate);
1678
+ TypedData_Get_Struct(self, ContextInfo, &context_type, context_info);
1679
+
1680
+ Isolate* isolate = context_info->isolate_info->isolate;
1681
+
1682
+ // flag for termination
1683
+ IsolateData::Set(isolate, IsolateData::DO_TERMINATE, true);
1684
+
1685
+ isolate->TerminateExecution();
1686
+ rb_funcall(self, rb_intern("stop_attached"), 0);
1687
+
515
1688
  return Qnil;
516
1689
  }
517
1690
 
518
- extern "C" {
1691
+ static VALUE
1692
+ rb_context_dispose(VALUE self) {
1693
+
1694
+ ContextInfo* context_info;
1695
+ TypedData_Get_Struct(self, ContextInfo, &context_type, context_info);
1696
+
1697
+ free_context(context_info);
1698
+
1699
+ return Qnil;
1700
+ }
1701
+
1702
+ static void*
1703
+ nogvl_context_call(void *args) {
1704
+
1705
+ FunctionCall *call = (FunctionCall *) args;
1706
+ if (!call) {
1707
+ return NULL;
1708
+ }
1709
+ IsolateInfo *isolate_info = call->context_info->isolate_info;
1710
+ Isolate* isolate = isolate_info->isolate;
1711
+
1712
+ IsolateData::Set(isolate, IsolateData::IN_GVL, false);
1713
+ IsolateData::Set(isolate, IsolateData::DO_TERMINATE, false);
1714
+
1715
+ if (call->max_memory > 0) {
1716
+ IsolateData::Set(isolate, IsolateData::MEM_SOFTLIMIT_MAX, call->max_memory);
1717
+ IsolateData::Set(isolate, IsolateData::MEM_SOFTLIMIT_REACHED, false);
1718
+ if (!isolate_info->added_gc_cb) {
1719
+ isolate->AddGCEpilogueCallback(gc_callback);
1720
+ isolate_info->added_gc_cb = true;
1721
+ }
1722
+ }
1723
+
1724
+ if (call->marshal_stackdepth > 0) {
1725
+ StackCounter::SetMax(isolate, call->marshal_stackdepth);
1726
+ }
1727
+
1728
+ Isolate::Scope isolate_scope(isolate);
1729
+ EscapableHandleScope handle_scope(isolate);
1730
+ TryCatch trycatch(isolate);
1731
+
1732
+ Local<Context> context = call->context_info->context->Get(isolate);
1733
+ Context::Scope context_scope(context);
1734
+
1735
+ Local<Function> fun = call->fun;
1736
+
1737
+ EvalResult& eval_res = call->result;
1738
+ eval_res.parsed = true;
1739
+
1740
+ MaybeLocal<v8::Value> res = fun->Call(context, context->Global(), call->argc, call->argv);
1741
+ prepare_result(res, trycatch, isolate, context, eval_res);
1742
+
1743
+ IsolateData::Set(isolate, IsolateData::IN_GVL, true);
1744
+
1745
+ return NULL;
1746
+ }
1747
+
1748
+ static void unblock_function(void *args) {
1749
+ FunctionCall *call = (FunctionCall *) args;
1750
+ call->context_info->isolate_info->interrupted = true;
1751
+ }
1752
+
1753
+ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1754
+ ContextInfo* context_info;
1755
+ FunctionCall call;
1756
+ VALUE *call_argv = NULL;
519
1757
 
520
- void Init_mini_racer_extension ( void )
1758
+ TypedData_Get_Struct(self, ContextInfo, &context_type, context_info);
1759
+ Isolate* isolate = context_info->isolate_info->isolate;
1760
+
1761
+ if (argc < 1) {
1762
+ rb_raise(rb_eArgError, "need at least one argument %d", argc);
1763
+ }
1764
+
1765
+ VALUE function_name = argv[0];
1766
+ if (TYPE(function_name) != T_STRING) {
1767
+ rb_raise(rb_eTypeError, "first argument should be a String");
1768
+ }
1769
+
1770
+ char *fname = RSTRING_PTR(function_name);
1771
+ if (!fname) {
1772
+ return Qnil;
1773
+ }
1774
+
1775
+ call.context_info = context_info;
1776
+ call.error = false;
1777
+ call.function_name = fname;
1778
+ call.argc = argc - 1;
1779
+ call.argv = NULL;
1780
+ if (call.argc > 0) {
1781
+ // skip first argument which is the function name
1782
+ call_argv = argv + 1;
1783
+ }
1784
+
1785
+ call.max_memory = 0;
1786
+ VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
1787
+ if (mem_softlimit != Qnil) {
1788
+ unsigned long sl_int = NUM2ULONG(mem_softlimit);
1789
+ call.max_memory = (size_t)sl_int;
1790
+ }
1791
+
1792
+ call.marshal_stackdepth = 0;
1793
+ VALUE marshal_stackdepth = rb_iv_get(self, "@marshal_stack_depth");
1794
+ if (marshal_stackdepth != Qnil) {
1795
+ unsigned long sl_int = NUM2ULONG(marshal_stackdepth);
1796
+ call.marshal_stackdepth = (size_t)sl_int;
1797
+ }
1798
+
1799
+ bool missingFunction = false;
521
1800
  {
522
- VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
523
- VALUE rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1801
+ Locker lock(isolate);
1802
+ Isolate::Scope isolate_scope(isolate);
1803
+ HandleScope handle_scope(isolate);
1804
+
1805
+ Local<Context> context = context_info->context->Get(isolate);
1806
+ Context::Scope context_scope(context);
1807
+
1808
+ // examples of such usage can be found in
1809
+ // https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
1810
+ MaybeLocal<String> fname = String::NewFromUtf8(isolate, call.function_name);
1811
+ MaybeLocal<v8::Value> val;
1812
+ if (!fname.IsEmpty()) {
1813
+ val = context->Global()->Get(context, fname.ToLocalChecked());
1814
+ }
1815
+
1816
+ if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1817
+ missingFunction = true;
1818
+ } else {
1819
+
1820
+ Local<v8::Function> fun = Local<v8::Function>::Cast(val.ToLocalChecked());
1821
+ call.fun = fun;
1822
+ int fun_argc = call.argc;
1823
+
1824
+ if (fun_argc > 0) {
1825
+ call.argv = (v8::Local<Value> *) malloc(sizeof(void *) * fun_argc);
1826
+ if (!call.argv) {
1827
+ return Qnil;
1828
+ }
1829
+ for(int i=0; i < fun_argc; i++) {
1830
+ call.argv[i] = convert_ruby_to_v8(isolate, context, call_argv[i]);
1831
+ }
1832
+ }
1833
+ rb_thread_call_without_gvl(nogvl_context_call, &call, unblock_function, &call);
1834
+ free(call.argv);
1835
+
1836
+ }
1837
+ }
1838
+
1839
+ if (missingFunction) {
1840
+ rb_raise(rb_eScriptRuntimeError, "Unknown JavaScript method invoked");
1841
+ }
1842
+
1843
+ return convert_result_to_ruby(self, call.result);
1844
+ }
1845
+
1846
+ static VALUE rb_context_create_isolate_value(VALUE self) {
1847
+ ContextInfo* context_info;
1848
+ TypedData_Get_Struct(self, ContextInfo, &context_type, context_info);
1849
+ IsolateInfo *isolate_info = context_info->isolate_info;
1850
+
1851
+ if (!isolate_info) {
1852
+ return Qnil;
1853
+ }
524
1854
 
525
- VALUE rb_eEvalError = rb_define_class_under(rb_mMiniRacer, "EvalError", rb_eStandardError);
526
- rb_eScriptTerminatedError = rb_define_class_under(rb_mMiniRacer, "ScriptTerminatedError", rb_eEvalError);
527
- rb_eParseError = rb_define_class_under(rb_mMiniRacer, "ParseError", rb_eEvalError);
528
- rb_eScriptRuntimeError = rb_define_class_under(rb_mMiniRacer, "RuntimeError", rb_eEvalError);
529
- rb_cJavaScriptFunction = rb_define_class_under(rb_mMiniRacer, "JavaScriptFunction", rb_cObject);
1855
+ isolate_info->hold();
1856
+ return TypedData_Wrap_Struct(rb_cIsolate, &isolate_type, isolate_info);
1857
+ }
1858
+
1859
+ static void set_ruby_exiting(VALUE value) {
1860
+ (void)value;
530
1861
 
531
- VALUE rb_cExternalFunction = rb_define_class_under(rb_cContext, "ExternalFunction", rb_cObject);
532
- rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
533
- rb_define_alloc_func(rb_cContext, allocate);
1862
+ int res = pthread_rwlock_wrlock(&exit_lock);
534
1863
 
535
- rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 1);
536
- rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
537
- rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
1864
+ ruby_exiting = true;
1865
+ if (res == 0) {
1866
+ pthread_rwlock_unlock(&exit_lock);
538
1867
  }
1868
+ }
539
1869
 
1870
+ extern "C" {
1871
+
1872
+ __attribute__((visibility("default"))) void Init_mini_racer_extension ( void )
1873
+ {
1874
+ VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
1875
+ rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1876
+ rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
1877
+ rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
1878
+ VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
1879
+
1880
+ VALUE rb_eError = rb_define_class_under(rb_mMiniRacer, "Error", rb_eStandardError);
1881
+
1882
+ VALUE rb_eEvalError = rb_define_class_under(rb_mMiniRacer, "EvalError", rb_eError);
1883
+ rb_eScriptTerminatedError = rb_define_class_under(rb_mMiniRacer, "ScriptTerminatedError", rb_eEvalError);
1884
+ rb_eV8OutOfMemoryError = rb_define_class_under(rb_mMiniRacer, "V8OutOfMemoryError", rb_eEvalError);
1885
+ rb_eParseError = rb_define_class_under(rb_mMiniRacer, "ParseError", rb_eEvalError);
1886
+ rb_eScriptRuntimeError = rb_define_class_under(rb_mMiniRacer, "RuntimeError", rb_eEvalError);
1887
+
1888
+ rb_cJavaScriptFunction = rb_define_class_under(rb_mMiniRacer, "JavaScriptFunction", rb_cObject);
1889
+ rb_eSnapshotError = rb_define_class_under(rb_mMiniRacer, "SnapshotError", rb_eError);
1890
+ rb_ePlatformAlreadyInitializedError = rb_define_class_under(rb_mMiniRacer, "PlatformAlreadyInitialized", rb_eError);
1891
+ rb_cFailedV8Conversion = rb_define_class_under(rb_mMiniRacer, "FailedV8Conversion", rb_cObject);
1892
+ rb_mJSON = rb_define_module("JSON");
1893
+
1894
+ VALUE rb_cExternalFunction = rb_define_class_under(rb_cContext, "ExternalFunction", rb_cObject);
1895
+
1896
+ rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
1897
+ rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
1898
+ rb_define_method(rb_cContext, "heap_stats", (VALUE(*)(...))&rb_heap_stats, 0);
1899
+ rb_define_method(rb_cContext, "write_heap_snapshot_unsafe", (VALUE(*)(...))&rb_heap_snapshot, 1);
1900
+
1901
+ rb_define_private_method(rb_cContext, "create_isolate_value",(VALUE(*)(...))&rb_context_create_isolate_value, 0);
1902
+ rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
1903
+ rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
1904
+ rb_define_private_method(rb_cContext, "isolate_mutex", (VALUE(*)(...))&rb_context_isolate_mutex, 0);
1905
+ rb_define_private_method(rb_cContext, "init_unsafe",(VALUE(*)(...))&rb_context_init_unsafe, 2);
1906
+
1907
+ rb_define_alloc_func(rb_cContext, allocate);
1908
+ rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
1909
+ rb_define_alloc_func(rb_cIsolate, allocate_isolate);
1910
+
1911
+ rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
1912
+
1913
+ rb_define_method(rb_cSnapshot, "size", (VALUE(*)(...))&rb_snapshot_size, 0);
1914
+ rb_define_method(rb_cSnapshot, "dump", (VALUE(*)(...))&rb_snapshot_dump, 0);
1915
+ rb_define_method(rb_cSnapshot, "warmup_unsafe!", (VALUE(*)(...))&rb_snapshot_warmup_unsafe, 1);
1916
+ rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1917
+
1918
+ rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
1919
+ rb_define_method(rb_cIsolate, "low_memory_notification", (VALUE(*)(...))&rb_isolate_low_memory_notification, 0);
1920
+ rb_define_method(rb_cIsolate, "pump_message_loop", (VALUE(*)(...))&rb_isolate_pump_message_loop, 0);
1921
+ rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
1922
+
1923
+ rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
1924
+
1925
+ rb_set_end_proc(set_ruby_exiting, Qnil);
1926
+
1927
+ static pthread_attr_t attr;
1928
+ if (pthread_attr_init(&attr) == 0) {
1929
+ if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0) {
1930
+ thread_attr_p = &attr;
1931
+ }
1932
+ }
1933
+ auto on_fork_for_child = []() {
1934
+ exit_lock = PTHREAD_RWLOCK_INITIALIZER;
1935
+ };
1936
+ pthread_atfork(nullptr, nullptr, on_fork_for_child);
1937
+ }
540
1938
  }