mini_racer 0.1.15 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d6c94af48c76bdd1d4ce6744e81cf812192498d8
4
- data.tar.gz: dd14708bbca74f0845ce37310fb4a7679326e7cb
2
+ SHA256:
3
+ metadata.gz: b2bb1ce568e44bcf3f6fcc469942ee12bd418cb21faaedbf1ed60a1b89e4ee47
4
+ data.tar.gz: 4a38af517388189628e22c7f9bf6b8129489847ff1b4d79b741e248b0f88fc6d
5
5
  SHA512:
6
- metadata.gz: 8982f3a4a7aa6c9f89a581099c48a2e694930f9e4f48beaf88cfaf38f9a21004b32fd5093937ffa0096ac4ebb725c7f99402928a0ab4b54153114fa81267d21b
7
- data.tar.gz: f9219565baccdea2ce89be3e50012b204e4482296f83084517db0e996a4a6147050230f6afcebc4ba79e1b127aa3dcbe845962c3087c5dd3ec3b2e16970537f0
6
+ metadata.gz: 9e8f42aad8b92fc27e9d430edea330fd583743ea7c81c6bbb5097de797af02b2905b87006798559e61e95ab2a0a9fb667e4d0758a3021876c81be138bc890e3d
7
+ data.tar.gz: 5e17012b06081f0b3f303f549908c348bed54ef9151069c1a6efcfb778708fde80fe2108a6f5f9f9908a9aec52449ad65a85fd2f0ae49892d89dfba59cc568c2
@@ -1,19 +1,23 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.1
4
- - 2.2
5
- - 2.3.3
6
- - 2.4.0
3
+ - 2.3
4
+ - 2.4
5
+ - 2.5
6
+ - ruby-head
7
7
  matrix:
8
8
  include:
9
+ - rvm: 2.5.1
10
+ os: osx
11
+ osx_image: xcode9.4
9
12
  - rvm: 2.4.0
10
13
  os: osx
11
- osx_image: xcode8.2
14
+ osx_image: xcode8.3
12
15
  - rvm: 2.4.0
13
16
  os: osx
14
17
  osx_image: xcode7.3
15
18
  dist: trusty
16
19
  sudo: true
17
20
  before_install:
18
- - gem install bundler -v 1.12.0
21
+ - gem update --system
22
+ - gem install bundler -v 1.16.2
19
23
  cache: bundler
data/CHANGELOG CHANGED
@@ -1,3 +1,12 @@
1
+ - 06-07-2018
2
+
3
+ - 0.2.0
4
+ - FEATURE: context#call to allow for cheaper invocation of functions
5
+ - FIX: rare memory leak when terminating a long running attached function
6
+ - FIX: rare segfault when terminating a long running attached function
7
+ - FIX: Reimplement Isolate#idle_notification using idle_notification_deadline, API remains the same @ignisf
8
+ - Account for changes in the upstream V8 API @ignisf
9
+ - Support for libv8 6.7
1
10
 
2
11
  23-08-2017
3
12
 
data/README.md CHANGED
@@ -10,6 +10,12 @@ It was created as an alternative to the excellent [therubyracer](https://github.
10
10
 
11
11
  MiniRacer has an adapter for [execjs](https://github.com/rails/execjs) so it can be used directly with Rails projects to minify assets, run babel or compile CoffeeScript.
12
12
 
13
+ ### A note about Ruby version Support
14
+
15
+ MiniRacer only supports non-EOL versions of Ruby. See [Ruby](https://www.ruby-lang.org/en/downloads) to see the list of non-EOL Rubies.
16
+
17
+ If you require support for older versions of Ruby install an older version of the gem.
18
+
13
19
  ## Features
14
20
 
15
21
  ### Simple eval for JavaScript
@@ -283,6 +289,22 @@ context.eval("a = 2")
283
289
  # nothing works on the context from now on, its a shell waiting to be disposed
284
290
  ```
285
291
 
292
+ ### Function call
293
+
294
+ This calls the function passed as first argument:
295
+
296
+ ```ruby
297
+ context = MiniRacer::Context.new
298
+ context.eval('function hello(name) { return "Hello, " + name + "!" }')
299
+ context.call('hello', 'George')
300
+ # "Hello, George!"
301
+ ```
302
+
303
+ Performance is slightly better than running `eval('hello("George")')` since:
304
+
305
+ - compilation of eval'd string is avoided
306
+ - function arguments don't need to be converted to JSON
307
+
286
308
  ## Performance
287
309
 
288
310
  The `bench` folder contains benchmark.
@@ -9,6 +9,7 @@ $CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic"
9
9
  $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or RUBY_PLATFORM =~ /darwin/
10
10
  $CPPFLAGS += " -std=c++0x"
11
11
  $CPPFLAGS += " -fpermissive"
12
+ $CPPFLAGS += " -Wno-reserved-user-defined-literal" if RUBY_PLATFORM =~ /darwin/
12
13
 
13
14
  $LDFLAGS.insert 0, " -stdlib=libstdc++ " if RUBY_PLATFORM =~ /darwin/
14
15
 
@@ -7,6 +7,7 @@
7
7
  #include <pthread.h>
8
8
  #include <unistd.h>
9
9
  #include <mutex>
10
+ #include <atomic>
10
11
  #include <math.h>
11
12
 
12
13
  using namespace v8;
@@ -16,21 +17,77 @@ typedef struct {
16
17
  int raw_size;
17
18
  } SnapshotInfo;
18
19
 
19
- typedef struct {
20
+ class IsolateInfo {
21
+ public:
20
22
  Isolate* isolate;
21
23
  ArrayBuffer::Allocator* allocator;
22
24
  StartupData* startup_data;
23
25
  bool interrupted;
24
- bool disposed;
25
26
  pid_t pid;
27
+ VALUE mutex;
28
+
29
+ class Lock {
30
+ VALUE &mutex;
31
+
32
+ public:
33
+ Lock(VALUE &mutex) : mutex(mutex) {
34
+ rb_mutex_lock(mutex);
35
+ }
36
+ ~Lock() {
37
+ rb_mutex_unlock(mutex);
38
+ }
39
+ };
40
+
41
+
42
+ IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
43
+ interrupted(false), pid(getpid()), refs_count(0) {
44
+ VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
45
+ mutex = rb_class_new_instance(0, nullptr, cMutex);
46
+ }
47
+
48
+ ~IsolateInfo() {
49
+ void free_isolate(IsolateInfo*);
50
+ free_isolate(this);
51
+ }
52
+
53
+ void init(SnapshotInfo* snapshot_info = nullptr);
54
+
55
+ void mark() {
56
+ rb_gc_mark(mutex);
57
+ }
58
+
59
+ Lock createLock() {
60
+ Lock lock(mutex);
61
+ return lock;
62
+ }
63
+
64
+ void hold() {
65
+ refs_count++;
66
+ }
67
+ void release() {
68
+ if (--refs_count <= 0) {
69
+ delete this;
70
+ }
71
+ }
72
+
73
+ static void* operator new(size_t size) {
74
+ return ruby_xmalloc(size);
75
+ }
26
76
 
77
+ static void operator delete(void *block) {
78
+ xfree(block);
79
+ }
80
+ private:
27
81
  // how many references to this isolate exist
28
- // we can't rely on Ruby's GC for this, because when destroying
29
- // objects, Ruby will destroy ruby objects first, then call the
30
- // extenstion's deallocators. In this case, that means it would
31
- // call `deallocate_isolate` _before_ `deallocate`, causing a segfault
32
- volatile int refs_count;
33
- } IsolateInfo;
82
+ // we can't rely on Ruby's GC for this, because Ruby could destroy the
83
+ // isolate before destroying the contexts that depend on them. We'd need to
84
+ // keep a list of linked contexts in the isolate to destroy those first when
85
+ // isolate destruction was requested. Keeping such a list would require
86
+ // notification from the context VALUEs when they are constructed and
87
+ // destroyed. With a ref count, those notifications are still needed, but
88
+ // we keep a simple int rather than a list of pointers.
89
+ std::atomic_int refs_count;
90
+ };
34
91
 
35
92
  typedef struct {
36
93
  IsolateInfo* isolate_info;
@@ -56,6 +113,16 @@ typedef struct {
56
113
  size_t max_memory;
57
114
  } EvalParams;
58
115
 
116
+ typedef struct {
117
+ ContextInfo *context_info;
118
+ char *function_name;
119
+ int argc;
120
+ bool error;
121
+ Local<Function> fun;
122
+ Local<Value> *argv;
123
+ EvalResult result;
124
+ } FunctionCall;
125
+
59
126
  enum IsolateFlags {
60
127
  IN_GVL,
61
128
  DO_TERMINATE,
@@ -63,6 +130,10 @@ enum IsolateFlags {
63
130
  MEM_SOFTLIMIT_REACHED,
64
131
  };
65
132
 
133
+ static VALUE rb_cContext;
134
+ static VALUE rb_cSnapshot;
135
+ static VALUE rb_cIsolate;
136
+
66
137
  static VALUE rb_eScriptTerminatedError;
67
138
  static VALUE rb_eV8OutOfMemoryError;
68
139
  static VALUE rb_eParseError;
@@ -81,6 +152,11 @@ static std::mutex platform_lock;
81
152
  static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
82
153
  bool platform_already_initialized = false;
83
154
 
155
+ if(TYPE(flag_as_str) != T_STRING) {
156
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE" (should be a string)",
157
+ rb_obj_class(flag_as_str));
158
+ }
159
+
84
160
  platform_lock.lock();
85
161
 
86
162
  if (current_platform == NULL) {
@@ -126,7 +202,86 @@ static void gc_callback(Isolate *isolate, GCType type, GCCallbackFlags flags) {
126
202
 
127
203
  if(used > softlimit) {
128
204
  isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)true);
129
- V8::TerminateExecution(isolate);
205
+ isolate->TerminateExecution();
206
+ }
207
+ }
208
+
209
+ // to be called with active lock and scope
210
+ static void prepare_result(MaybeLocal<Value> v8res,
211
+ TryCatch& trycatch,
212
+ Isolate* isolate,
213
+ Local<Context> context,
214
+ EvalResult& evalRes /* out */) {
215
+
216
+ // just don't touch .parsed
217
+ evalRes.terminated = false;
218
+ evalRes.json = false;
219
+ evalRes.value = nullptr;
220
+ evalRes.message = nullptr;
221
+ evalRes.backtrace = nullptr;
222
+ evalRes.executed = !v8res.IsEmpty();
223
+
224
+ if (evalRes.executed) {
225
+ // arrays and objects get converted to json
226
+ Local<Value> local_value = v8res.ToLocalChecked();
227
+ if ((local_value->IsObject() || local_value->IsArray()) &&
228
+ !local_value->IsDate() && !local_value->IsFunction()) {
229
+ Local<Object> JSON = context->Global()->Get(
230
+ String::NewFromUtf8(isolate, "JSON"))->ToObject();
231
+
232
+ Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
233
+ .As<Function>();
234
+
235
+ Local<Object> object = local_value->ToObject();
236
+ const unsigned argc = 1;
237
+ Local<Value> argv[argc] = { object };
238
+ MaybeLocal<Value> json = stringify->Call(JSON, argc, argv);
239
+
240
+ if (json.IsEmpty()) {
241
+ evalRes.executed = false;
242
+ } else {
243
+ evalRes.json = true;
244
+ Persistent<Value>* persistent = new Persistent<Value>();
245
+ persistent->Reset(isolate, json.ToLocalChecked());
246
+ evalRes.value = persistent;
247
+ }
248
+
249
+ } else {
250
+ Persistent<Value>* persistent = new Persistent<Value>();
251
+ persistent->Reset(isolate, local_value);
252
+ evalRes.value = persistent;
253
+ }
254
+ }
255
+
256
+ if (!evalRes.executed || !evalRes.parsed) {
257
+ if (trycatch.HasCaught()) {
258
+ if (!trycatch.Exception()->IsNull()) {
259
+ evalRes.message = new Persistent<Value>();
260
+ Local<Message> message = trycatch.Message();
261
+ char buf[1000];
262
+ int len;
263
+ len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(message->Get()),
264
+ *String::Utf8Value(message->GetScriptResourceName()->ToString()),
265
+ message->GetLineNumber(),
266
+ message->GetStartColumn());
267
+ if ((size_t) len >= sizeof(buf)) {
268
+ len = sizeof(buf) - 1;
269
+ buf[len] = '\0';
270
+ }
271
+
272
+ Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, len).ToLocalChecked();
273
+ evalRes.message->Reset(isolate, v8_message);
274
+ } else if(trycatch.HasTerminated()) {
275
+ evalRes.terminated = true;
276
+ evalRes.message = new Persistent<Value>();
277
+ Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
278
+ evalRes.message->Reset(isolate, tmp);
279
+ }
280
+ if (!trycatch.StackTrace().IsEmpty()) {
281
+ evalRes.backtrace = new Persistent<Value>();
282
+ evalRes.backtrace->Reset(isolate, trycatch.StackTrace()->ToString());
283
+ }
284
+ }
130
285
  }
131
286
  }
132
287
 
@@ -171,90 +326,30 @@ nogvl_context_eval(void* arg) {
171
326
  result->json = false;
172
327
  result->value = NULL;
173
328
 
329
+ MaybeLocal<Value> maybe_value;
174
330
  if (!result->parsed) {
175
331
  result->message = new Persistent<Value>();
176
332
  result->message->Reset(isolate, trycatch.Exception());
177
333
  } else {
334
+ // parsing successful
335
+ if (eval_params->max_memory > 0) {
336
+ isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
337
+ isolate->AddGCEpilogueCallback(gc_callback);
338
+ }
178
339
 
179
- if(eval_params->max_memory > 0) {
180
- isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
181
- isolate->AddGCEpilogueCallback(gc_callback);
182
- }
183
-
184
- MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
185
-
186
- result->executed = !maybe_value.IsEmpty();
187
-
188
- if (result->executed) {
189
-
190
- // arrays and objects get converted to json
191
- Local<Value> local_value = maybe_value.ToLocalChecked();
192
- if ((local_value->IsObject() || local_value->IsArray()) &&
193
- !local_value->IsDate() && !local_value->IsFunction()) {
194
- Local<Object> JSON = context->Global()->Get(
195
- String::NewFromUtf8(isolate, "JSON"))->ToObject();
196
-
197
- Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
198
- .As<Function>();
199
-
200
- Local<Object> object = local_value->ToObject();
201
- const unsigned argc = 1;
202
- Local<Value> argv[argc] = { object };
203
- MaybeLocal<Value> json = stringify->Call(JSON, argc, argv);
204
-
205
- if (json.IsEmpty()) {
206
- result->executed = false;
207
- } else {
208
- result->json = true;
209
- Persistent<Value>* persistent = new Persistent<Value>();
210
- persistent->Reset(isolate, json.ToLocalChecked());
211
- result->value = persistent;
212
- }
213
-
214
- } else {
215
- Persistent<Value>* persistent = new Persistent<Value>();
216
- persistent->Reset(isolate, local_value);
217
- result->value = persistent;
218
- }
219
- }
340
+ maybe_value = parsed_script.ToLocalChecked()->Run(context);
220
341
  }
221
342
 
222
- if (!result->executed || !result->parsed) {
223
- if (trycatch.HasCaught()) {
224
- if (!trycatch.Exception()->IsNull()) {
225
- result->message = new Persistent<Value>();
226
- Local<Message> message = trycatch.Message();
227
- char buf[1000];
228
- int len;
229
- len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(message->Get()),
230
- *String::Utf8Value(message->GetScriptResourceName()->ToString()),
231
- message->GetLineNumber(),
232
- message->GetStartColumn());
233
-
234
- Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, (int)len).ToLocalChecked();
235
- result->message->Reset(isolate, v8_message);
236
- } else if(trycatch.HasTerminated()) {
237
-
238
-
239
- result->terminated = true;
240
- result->message = new Persistent<Value>();
241
- Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
242
- result->message->Reset(isolate, tmp);
243
- }
244
- if (!trycatch.StackTrace().IsEmpty()) {
245
- result->backtrace = new Persistent<Value>();
246
- result->backtrace->Reset(isolate, trycatch.StackTrace()->ToString());
247
- }
248
- }
249
- }
343
+ prepare_result(maybe_value, trycatch, isolate, context, *result);
250
344
 
251
345
  isolate->SetData(IN_GVL, (void*)true);
252
346
 
253
-
254
347
  return NULL;
255
348
  }
256
349
 
257
- static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
350
+ // assumes isolate locking is in place
351
+ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
352
+ Local<Value> value) {
258
353
 
259
354
  Isolate::Scope isolate_scope(isolate);
260
355
  HandleScope scope(isolate);
@@ -284,7 +379,7 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
284
379
  Local<Array> arr = Local<Array>::Cast(value);
285
380
  for(uint32_t i=0; i < arr->Length(); i++) {
286
381
  Local<Value> element = arr->Get(i);
287
- VALUE rb_elem = convert_v8_to_ruby(isolate, element);
382
+ VALUE rb_elem = convert_v8_to_ruby(isolate, context, element);
288
383
  if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
289
384
  return rb_elem;
290
385
  }
@@ -308,16 +403,15 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
308
403
  if (value->IsObject()) {
309
404
  VALUE rb_hash = rb_hash_new();
310
405
  TryCatch trycatch(isolate);
311
- Local<Context> context = Context::New(isolate);
312
406
 
313
407
  Local<Object> object = value->ToObject();
314
- MaybeLocal<Array> maybe_props = object->GetOwnPropertyNames(context);
408
+ auto maybe_props = object->GetOwnPropertyNames(context);
315
409
  if (!maybe_props.IsEmpty()) {
316
410
  Local<Array> props = maybe_props.ToLocalChecked();
317
411
  for(uint32_t i=0; i < props->Length(); i++) {
318
412
  Local<Value> key = props->Get(i);
319
- VALUE rb_key = convert_v8_to_ruby(isolate, key);
320
- Local<Value> value = object->Get(key);
413
+ VALUE rb_key = convert_v8_to_ruby(isolate, context, key);
414
+ Local<Value> prop_value = object->Get(key);
321
415
  // this may have failed due to Get raising
322
416
 
323
417
  if (trycatch.HasCaught()) {
@@ -326,7 +420,7 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
326
420
  return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
327
421
  }
328
422
 
329
- VALUE rb_value = convert_v8_to_ruby(isolate, value);
423
+ VALUE rb_value = convert_v8_to_ruby(isolate, context, prop_value);
330
424
  rb_hash_aset(rb_hash, rb_key, rb_value);
331
425
  }
332
426
  }
@@ -337,7 +431,25 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
337
431
  return rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
338
432
  }
339
433
 
340
- static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
434
+ static VALUE convert_v8_to_ruby(Isolate* isolate,
435
+ const Persistent<Context>& context,
436
+ Local<Value> value) {
437
+ HandleScope scope(isolate);
438
+ return convert_v8_to_ruby(isolate,
439
+ Local<Context>::New(isolate, context),
440
+ value);
441
+ }
442
+
443
+ static VALUE convert_v8_to_ruby(Isolate* isolate,
444
+ const Persistent<Context>& context,
445
+ const Persistent<Value>& value) {
446
+ HandleScope scope(isolate);
447
+ return convert_v8_to_ruby(isolate,
448
+ Local<Context>::New(isolate, context),
449
+ Local<Value>::New(isolate, value));
450
+ }
451
+
452
+ static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
341
453
  EscapableHandleScope scope(isolate);
342
454
 
343
455
  Local<Array> array;
@@ -431,6 +543,11 @@ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
431
543
  SnapshotInfo* snapshot_info;
432
544
  Data_Get_Struct(self, SnapshotInfo, snapshot_info);
433
545
 
546
+ if(TYPE(str) != T_STRING) {
547
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
548
+ rb_obj_class(str));
549
+ }
550
+
434
551
  init_v8();
435
552
 
436
553
  StartupData startup_data = V8::CreateSnapshotDataBlob(RSTRING_PTR(str));
@@ -445,10 +562,15 @@ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
445
562
  return Qnil;
446
563
  }
447
564
 
448
- static VALUE rb_snapshot_warmup(VALUE self, VALUE str) {
565
+ static VALUE rb_snapshot_warmup_unsafe(VALUE self, VALUE str) {
449
566
  SnapshotInfo* snapshot_info;
450
567
  Data_Get_Struct(self, SnapshotInfo, snapshot_info);
451
568
 
569
+ if(TYPE(str) != T_STRING) {
570
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
571
+ rb_obj_class(str));
572
+ }
573
+
452
574
  init_v8();
453
575
 
454
576
  StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
@@ -466,27 +588,16 @@ static VALUE rb_snapshot_warmup(VALUE self, VALUE str) {
466
588
  return self;
467
589
  }
468
590
 
469
- static VALUE rb_isolate_init_with_snapshot(VALUE self, VALUE snapshot) {
470
- IsolateInfo* isolate_info;
471
- Data_Get_Struct(self, IsolateInfo, isolate_info);
472
-
473
- init_v8();
474
-
475
- isolate_info->allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
476
- isolate_info->interrupted = false;
477
- isolate_info->refs_count = 1;
591
+ void IsolateInfo::init(SnapshotInfo* snapshot_info) {
592
+ allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
478
593
 
479
594
  Isolate::CreateParams create_params;
480
- create_params.array_buffer_allocator = isolate_info->allocator;
481
-
482
- StartupData* startup_data = NULL;
483
- if (!NIL_P(snapshot)) {
484
- SnapshotInfo* snapshot_info;
485
- Data_Get_Struct(snapshot, SnapshotInfo, snapshot_info);
595
+ create_params.array_buffer_allocator = allocator;
486
596
 
597
+ if (snapshot_info) {
487
598
  int raw_size = snapshot_info->raw_size;
488
599
  char* data = new char[raw_size];
489
- memcpy(data, snapshot_info->data, sizeof(char) * raw_size);
600
+ memcpy(data, snapshot_info->data, raw_size);
490
601
 
491
602
  startup_data = new StartupData;
492
603
  startup_data->data = data;
@@ -495,8 +606,22 @@ static VALUE rb_isolate_init_with_snapshot(VALUE self, VALUE snapshot) {
495
606
  create_params.snapshot_blob = startup_data;
496
607
  }
497
608
 
498
- isolate_info->startup_data = startup_data;
499
- isolate_info->isolate = Isolate::New(create_params);
609
+ isolate = Isolate::New(create_params);
610
+ }
611
+
612
+ static VALUE rb_isolate_init_with_snapshot(VALUE self, VALUE snapshot) {
613
+ IsolateInfo* isolate_info;
614
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
615
+
616
+ init_v8();
617
+
618
+ SnapshotInfo* snapshot_info = nullptr;
619
+ if (!NIL_P(snapshot)) {
620
+ Data_Get_Struct(snapshot, SnapshotInfo, snapshot_info);
621
+ }
622
+
623
+ isolate_info->init(snapshot_info);
624
+ isolate_info->hold();
500
625
 
501
626
  return Qnil;
502
627
  }
@@ -505,23 +630,40 @@ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
505
630
  IsolateInfo* isolate_info;
506
631
  Data_Get_Struct(self, IsolateInfo, isolate_info);
507
632
 
508
- return isolate_info->isolate->IdleNotification(NUM2INT(idle_time_in_ms)) ? Qtrue : Qfalse;
633
+ if (current_platform == NULL) return Qfalse;
634
+
635
+ double duration = NUM2DBL(idle_time_in_ms) / 1000.0;
636
+ double now = current_platform->MonotonicallyIncreasingTime();
637
+ return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
509
638
  }
510
639
 
511
- static VALUE rb_context_init_with_isolate(VALUE self, VALUE isolate) {
640
+ static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
512
641
  ContextInfo* context_info;
513
642
  Data_Get_Struct(self, ContextInfo, context_info);
514
643
 
515
644
  init_v8();
516
645
 
517
646
  IsolateInfo* isolate_info;
518
- Data_Get_Struct(isolate, IsolateInfo, isolate_info);
647
+
648
+ if (NIL_P(isolate) || !rb_obj_is_kind_of(isolate, rb_cIsolate)) {
649
+ isolate_info = new IsolateInfo();
650
+
651
+ SnapshotInfo *snapshot_info = nullptr;
652
+ if (!NIL_P(snap) && rb_obj_is_kind_of(snap, rb_cSnapshot)) {
653
+ Data_Get_Struct(snap, SnapshotInfo, snapshot_info);
654
+ }
655
+ isolate_info->init(snapshot_info);
656
+ } else { // given isolate or snapshot
657
+ Data_Get_Struct(isolate, IsolateInfo, isolate_info);
658
+ }
519
659
 
520
660
  context_info->isolate_info = isolate_info;
521
- isolate_info->refs_count++;
661
+ isolate_info->hold();
522
662
 
523
663
  {
524
- Locker lock(isolate_info->isolate);
664
+ // the ruby lock is needed if this isn't a new isolate
665
+ IsolateInfo::Lock ruby_lock(isolate_info->mutex);
666
+ Locker lock(isolate_info->isolate);
525
667
  Isolate::Scope isolate_scope(isolate_info->isolate);
526
668
  HandleScope handle_scope(isolate_info->isolate);
527
669
 
@@ -539,19 +681,116 @@ static VALUE rb_context_init_with_isolate(VALUE self, VALUE isolate) {
539
681
  return Qnil;
540
682
  }
541
683
 
684
+ static VALUE convert_result_to_ruby(VALUE self /* context */,
685
+ EvalResult& result) {
686
+ ContextInfo *context_info;
687
+ Data_Get_Struct(self, ContextInfo, context_info);
688
+
689
+ Isolate *isolate = context_info->isolate_info->isolate;
690
+ Persistent<Context> *p_ctx = context_info->context;
691
+
692
+ VALUE message = Qnil;
693
+ VALUE backtrace = Qnil;
694
+ {
695
+ Locker lock(isolate);
696
+ if (result.message) {
697
+ message = convert_v8_to_ruby(isolate, *p_ctx, *result.message);
698
+ result.message->Reset();
699
+ delete result.message;
700
+ result.message = nullptr;
701
+ }
702
+
703
+ if (result.backtrace) {
704
+ backtrace = convert_v8_to_ruby(isolate, *p_ctx, *result.backtrace);
705
+ result.backtrace->Reset();
706
+ delete result.backtrace;
707
+ }
708
+ }
709
+
710
+ // NOTE: this is very important, we can not do an rb_raise from within
711
+ // a v8 scope, if we do the scope is never cleaned up properly and we leak
712
+ if (!result.parsed) {
713
+ if(TYPE(message) == T_STRING) {
714
+ rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
715
+ } else {
716
+ rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
717
+ }
718
+ }
719
+
720
+ if (!result.executed) {
721
+ VALUE ruby_exception = rb_iv_get(self, "@current_exception");
722
+ if (ruby_exception == Qnil) {
723
+ bool mem_softlimit_reached = (bool)isolate->GetData(MEM_SOFTLIMIT_REACHED);
724
+ // If we were terminated or have the memory softlimit flag set
725
+ if (result.terminated || mem_softlimit_reached) {
726
+ ruby_exception = mem_softlimit_reached ? rb_eV8OutOfMemoryError : rb_eScriptTerminatedError;
727
+ } else {
728
+ ruby_exception = rb_eScriptRuntimeError;
729
+ }
730
+
731
+ // exception report about what happened
732
+ if (TYPE(backtrace) == T_STRING) {
733
+ rb_raise(ruby_exception, "%s", RSTRING_PTR(backtrace));
734
+ } else if(TYPE(message) == T_STRING) {
735
+ rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
736
+ } else {
737
+ rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
738
+ }
739
+ } else {
740
+ VALUE rb_str = rb_funcall(ruby_exception, rb_intern("to_s"), 0);
741
+ rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
742
+ }
743
+ }
744
+
745
+ VALUE ret = Qnil;
746
+
747
+ // New scope for return value
748
+ {
749
+ Locker lock(isolate);
750
+ Isolate::Scope isolate_scope(isolate);
751
+ HandleScope handle_scope(isolate);
752
+
753
+ Local<Value> tmp = Local<Value>::New(isolate, *result.value);
754
+
755
+ if (result.json) {
756
+ Local<String> rstr = tmp->ToString();
757
+ VALUE json_string = rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
758
+ ret = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
759
+ } else {
760
+ ret = convert_v8_to_ruby(isolate, *p_ctx, tmp);
761
+ }
762
+
763
+ result.value->Reset();
764
+ delete result.value;
765
+ }
766
+
767
+ if (rb_funcall(ret, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
768
+ // TODO try to recover stack trace from the conversion error
769
+ rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
770
+ }
771
+
772
+
773
+ return ret;
774
+ }
775
+
542
776
  static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
543
777
 
544
778
  EvalParams eval_params;
545
779
  EvalResult eval_result;
546
780
  ContextInfo* context_info;
547
- VALUE result;
548
-
549
- VALUE message = Qnil;
550
- VALUE backtrace = Qnil;
551
781
 
552
782
  Data_Get_Struct(self, ContextInfo, context_info);
553
783
  Isolate* isolate = context_info->isolate_info->isolate;
554
784
 
785
+ if(TYPE(str) != T_STRING) {
786
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
787
+ rb_obj_class(str));
788
+ }
789
+ if(filename != Qnil && TYPE(filename) != T_STRING) {
790
+ rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be nil or a string)",
791
+ rb_obj_class(filename));
792
+ }
793
+
555
794
  {
556
795
  Locker lock(isolate);
557
796
  Isolate::Scope isolate_scope(isolate);
@@ -589,90 +828,15 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
589
828
  eval_result.backtrace = NULL;
590
829
 
591
830
  rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
592
-
593
- if (eval_result.message != NULL) {
594
- Local<Value> tmp = Local<Value>::New(isolate, *eval_result.message);
595
- message = convert_v8_to_ruby(isolate, tmp);
596
- eval_result.message->Reset();
597
- delete eval_result.message;
598
- }
599
-
600
- if (eval_result.backtrace != NULL) {
601
- Local<Value> tmp = Local<Value>::New(isolate, *eval_result.backtrace);
602
- backtrace = convert_v8_to_ruby(isolate, tmp);
603
- eval_result.backtrace->Reset();
604
- delete eval_result.backtrace;
605
- }
606
- }
607
-
608
- // NOTE: this is very important, we can not do an rb_raise from within
609
- // a v8 scope, if we do the scope is never cleaned up properly and we leak
610
- if (!eval_result.parsed) {
611
- if(TYPE(message) == T_STRING) {
612
- rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
613
- } else {
614
- rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
615
- }
616
- }
617
-
618
- if (!eval_result.executed) {
619
- VALUE ruby_exception = rb_iv_get(self, "@current_exception");
620
- if (ruby_exception == Qnil) {
621
- bool mem_softlimit_reached = (bool)isolate->GetData(MEM_SOFTLIMIT_REACHED);
622
- // If we were terminated or have the memory softlimit flag set
623
- if(eval_result.terminated || mem_softlimit_reached) {
624
- ruby_exception = mem_softlimit_reached ? rb_eV8OutOfMemoryError : rb_eScriptTerminatedError;
625
- } else {
626
- ruby_exception = rb_eScriptRuntimeError;
627
- }
628
-
629
- // exception report about what happened
630
- if(TYPE(backtrace) == T_STRING) {
631
- rb_raise(ruby_exception, "%s", RSTRING_PTR(backtrace));
632
- } else if(TYPE(message) == T_STRING) {
633
- rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
634
- } else {
635
- rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
636
- }
637
- } else {
638
- VALUE rb_str = rb_funcall(ruby_exception, rb_intern("to_s"), 0);
639
- rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
640
- }
641
- }
642
-
643
- // New scope for return value
644
- {
645
- Locker lock(isolate);
646
- Isolate::Scope isolate_scope(isolate);
647
- HandleScope handle_scope(isolate);
648
-
649
- Local<Value> tmp = Local<Value>::New(isolate, *eval_result.value);
650
-
651
- if (eval_result.json) {
652
- Local<String> rstr = tmp->ToString();
653
- VALUE json_string = rb_enc_str_new(*String::Utf8Value(rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
654
- result = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
655
- } else {
656
- result = convert_v8_to_ruby(isolate, tmp);
657
- }
658
-
659
- eval_result.value->Reset();
660
- delete eval_result.value;
661
- }
662
-
663
- if (rb_funcall(result, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
664
- // TODO try to recover stack trace from the conversion error
665
- rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
666
831
  }
667
832
 
668
-
669
- return result;
833
+ return convert_result_to_ruby(self, eval_result);
670
834
  }
671
835
 
672
836
  typedef struct {
673
837
  VALUE callback;
674
838
  int length;
675
- VALUE* args;
839
+ VALUE ruby_args;
676
840
  bool failed;
677
841
  } protected_callback_data;
678
842
 
@@ -682,7 +846,9 @@ VALUE protected_callback(VALUE rdata) {
682
846
  VALUE result;
683
847
 
684
848
  if (data->length > 0) {
685
- result = rb_funcall2(data->callback, rb_intern("call"), data->length, data->args);
849
+ result = rb_funcall2(data->callback, rb_intern("call"), data->length,
850
+ RARRAY_PTR(data->ruby_args));
851
+ RB_GC_GUARD(data->ruby_args);
686
852
  } else {
687
853
  result = rb_funcall(data->callback, rb_intern("call"), 0);
688
854
  }
@@ -700,28 +866,36 @@ void*
700
866
  gvl_ruby_callback(void* data) {
701
867
 
702
868
  FunctionCallbackInfo<Value>* args = (FunctionCallbackInfo<Value>*)data;
703
- VALUE* ruby_args = NULL;
869
+ VALUE ruby_args = Qnil;
704
870
  int length = args->Length();
705
871
  VALUE callback;
706
872
  VALUE result;
707
873
  VALUE self;
708
-
874
+ VALUE parent;
709
875
  {
710
- HandleScope scope(args->GetIsolate());
711
- Handle<External> external = Handle<External>::Cast(args->Data());
876
+ HandleScope scope(args->GetIsolate());
877
+ Local<External> external = Local<External>::Cast(args->Data());
712
878
 
713
- VALUE* self_pointer = (VALUE*)(external->Value());
714
- self = *self_pointer;
715
- callback = rb_iv_get(self, "@callback");
879
+ self = *(VALUE*)(external->Value());
880
+ callback = rb_iv_get(self, "@callback");
881
+
882
+ parent = rb_iv_get(self, "@parent");
883
+ if (NIL_P(parent) || !rb_obj_is_kind_of(parent, rb_cContext)) {
884
+ return NULL;
885
+ }
886
+
887
+ ContextInfo* context_info;
888
+ Data_Get_Struct(parent, ContextInfo, context_info);
716
889
 
717
890
  if (length > 0) {
718
- ruby_args = ALLOC_N(VALUE, length);
891
+ ruby_args = rb_ary_tmp_new(length);
719
892
  }
720
893
 
721
-
722
894
  for (int i = 0; i < length; i++) {
723
895
  Local<Value> value = ((*args)[i]).As<Value>();
724
- ruby_args[i] = convert_v8_to_ruby(args->GetIsolate(), value);
896
+ VALUE tmp = convert_v8_to_ruby(args->GetIsolate(),
897
+ *context_info->context, value);
898
+ rb_ary_push(ruby_args, tmp);
725
899
  }
726
900
  }
727
901
 
@@ -729,12 +903,16 @@ gvl_ruby_callback(void* data) {
729
903
  protected_callback_data callback_data;
730
904
  callback_data.length = length;
731
905
  callback_data.callback = callback;
732
- callback_data.args = ruby_args;
906
+ callback_data.ruby_args = ruby_args;
733
907
  callback_data.failed = false;
734
908
 
735
909
  if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
736
910
  args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Terminated execution during transition from Ruby to JS"));
737
- V8::TerminateExecution(args->GetIsolate());
911
+ args->GetIsolate()->TerminateExecution();
912
+ if (length > 0) {
913
+ rb_ary_clear(ruby_args);
914
+ rb_gc_force_recycle(ruby_args);
915
+ }
738
916
  return NULL;
739
917
  }
740
918
 
@@ -742,7 +920,6 @@ gvl_ruby_callback(void* data) {
742
920
  (VALUE(*)(...))&rescue_callback, (VALUE)(&callback_data), rb_eException, (VALUE)0);
743
921
 
744
922
  if(callback_data.failed) {
745
- VALUE parent = rb_iv_get(self, "@parent");
746
923
  rb_iv_set(parent, "@current_exception", result);
747
924
  args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
748
925
  }
@@ -753,27 +930,27 @@ gvl_ruby_callback(void* data) {
753
930
  }
754
931
 
755
932
  if (length > 0) {
756
- xfree(ruby_args);
933
+ rb_ary_clear(ruby_args);
934
+ rb_gc_force_recycle(ruby_args);
757
935
  }
758
936
 
759
937
  if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
760
938
  Isolate* isolate = args->GetIsolate();
761
- V8::TerminateExecution(isolate);
939
+ isolate->TerminateExecution();
762
940
  }
763
941
 
764
942
  return NULL;
765
943
  }
766
944
 
767
945
  static void ruby_callback(const FunctionCallbackInfo<Value>& args) {
768
-
769
946
  bool has_gvl = (bool)args.GetIsolate()->GetData(IN_GVL);
770
947
 
771
948
  if(has_gvl) {
772
949
  gvl_ruby_callback((void*)&args);
773
950
  } else {
774
- args.GetIsolate()->SetData(IN_GVL, (void*)true);
951
+ args.GetIsolate()->SetData(IN_GVL, (void*)true);
775
952
  rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
776
- args.GetIsolate()->SetData(IN_GVL, (void*)false);
953
+ args.GetIsolate()->SetData(IN_GVL, (void*)false);
777
954
  }
778
955
  }
779
956
 
@@ -838,16 +1015,27 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
838
1015
 
839
1016
  // always raise out of V8 context
840
1017
  if (parse_error) {
841
- rb_raise(rb_eParseError, "Invalid object %s", RSTRING_PTR(parent_object));
1018
+ rb_raise(rb_eParseError, "Invalid object %" PRIsVALUE, parent_object);
842
1019
  }
843
1020
 
844
1021
  if (attach_error) {
845
- rb_raise(rb_eParseError, "Was expecting %s to be an object", RSTRING_PTR(parent_object));
1022
+ rb_raise(rb_eParseError, "Was expecting %" PRIsVALUE" to be an object", parent_object);
846
1023
  }
847
1024
 
848
1025
  return Qnil;
849
1026
  }
850
1027
 
1028
+ static VALUE rb_context_isolate_mutex(VALUE self) {
1029
+ ContextInfo* context_info;
1030
+ Data_Get_Struct(self, ContextInfo, context_info);
1031
+
1032
+ if (!context_info->isolate_info) {
1033
+ rb_raise(rb_eScriptRuntimeError, "Context has no Isolate available anymore");
1034
+ }
1035
+
1036
+ return context_info->isolate_info->mutex;
1037
+ }
1038
+
851
1039
  void free_isolate(IsolateInfo* isolate_info) {
852
1040
 
853
1041
  if (isolate_info->isolate) {
@@ -874,26 +1062,11 @@ void free_isolate(IsolateInfo* isolate_info) {
874
1062
  }
875
1063
 
876
1064
  delete isolate_info->allocator;
877
- isolate_info->disposed = true;
878
1065
  }
879
1066
 
880
- void maybe_free_isolate(IsolateInfo* isolate_info) {
881
- // an isolate can only be freed if no Isolate or Context (ruby) object
882
- // still need it
883
- //
884
- // there is a sequence issue here where Ruby may call the deallocator on the
885
- // context object after it calles the dallocator on the isolate
886
- if (isolate_info->refs_count != 0 || isolate_info->disposed) {
887
- return;
888
- }
889
-
890
- free_isolate(isolate_info);
891
- }
892
-
893
-
894
- void free_context(void* data) {
1067
+ // destroys everything except freeing the ContextInfo struct (see deallocate())
1068
+ static void free_context(ContextInfo* context_info) {
895
1069
 
896
- ContextInfo* context_info = (ContextInfo*)data;
897
1070
  IsolateInfo* isolate_info = context_info->isolate_info;
898
1071
 
899
1072
  if (context_info->context && isolate_info && isolate_info->isolate) {
@@ -903,46 +1076,39 @@ void free_context(void* data) {
903
1076
  delete context_info->context;
904
1077
  context_info->context = NULL;
905
1078
  }
906
- }
907
-
908
- void deallocate_context(void* data) {
909
-
910
- ContextInfo* context_info = (ContextInfo*)data;
911
- IsolateInfo* isolate_info = context_info->isolate_info;
912
-
913
- free_context(data);
914
1079
 
915
1080
  if (isolate_info) {
916
- isolate_info->refs_count--;
917
- maybe_free_isolate(isolate_info);
1081
+ isolate_info->release();
1082
+ context_info->isolate_info = NULL;
918
1083
  }
919
1084
  }
920
1085
 
921
- void deallocate_isolate(void* data) {
1086
+ static void deallocate_isolate(void* data) {
922
1087
 
923
1088
  IsolateInfo* isolate_info = (IsolateInfo*) data;
924
1089
 
925
- isolate_info->refs_count--;
926
- maybe_free_isolate(isolate_info);
1090
+ isolate_info->release();
1091
+ }
927
1092
 
928
- if (isolate_info->refs_count == 0) {
929
- xfree(isolate_info);
930
- }
1093
+ static void mark_isolate(void* data) {
1094
+ IsolateInfo* isolate_info = (IsolateInfo*) data;
1095
+ isolate_info->mark();
931
1096
  }
932
1097
 
933
1098
  void deallocate(void* data) {
934
1099
  ContextInfo* context_info = (ContextInfo*)data;
935
- IsolateInfo* isolate_info = context_info->isolate_info;
936
1100
 
937
- deallocate_context(data);
938
-
939
- if (isolate_info && isolate_info->refs_count == 0) {
940
- xfree(isolate_info);
941
- }
1101
+ free_context(context_info);
942
1102
 
943
1103
  xfree(data);
944
1104
  }
945
1105
 
1106
+ static void mark_context(void* data) {
1107
+ ContextInfo* context_info = (ContextInfo*)data;
1108
+ if (context_info->isolate_info) {
1109
+ context_info->isolate_info->mark();
1110
+ }
1111
+ }
946
1112
 
947
1113
  void deallocate_external_function(void * data) {
948
1114
  xfree(data);
@@ -964,7 +1130,7 @@ VALUE allocate(VALUE klass) {
964
1130
  context_info->isolate_info = NULL;
965
1131
  context_info->context = NULL;
966
1132
 
967
- return Data_Wrap_Struct(klass, NULL, deallocate, (void*)context_info);
1133
+ return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
968
1134
  }
969
1135
 
970
1136
  VALUE allocate_snapshot(VALUE klass) {
@@ -976,17 +1142,9 @@ VALUE allocate_snapshot(VALUE klass) {
976
1142
  }
977
1143
 
978
1144
  VALUE allocate_isolate(VALUE klass) {
979
- IsolateInfo* isolate_info = ALLOC(IsolateInfo);
980
-
981
- isolate_info->isolate = NULL;
982
- isolate_info->allocator = NULL;
983
- isolate_info->startup_data = NULL;
984
- isolate_info->interrupted = false;
985
- isolate_info->refs_count = 0;
986
- isolate_info->pid = getpid();
987
- isolate_info->disposed = false;
1145
+ IsolateInfo* isolate_info = new IsolateInfo();
988
1146
 
989
- return Data_Wrap_Struct(klass, NULL, deallocate_isolate, (void*)isolate_info);
1147
+ return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
990
1148
  }
991
1149
 
992
1150
  static VALUE
@@ -1033,7 +1191,7 @@ rb_context_stop(VALUE self) {
1033
1191
  // flag for termination
1034
1192
  isolate->SetData(DO_TERMINATE, (void*)true);
1035
1193
 
1036
- V8::TerminateExecution(isolate);
1194
+ isolate->TerminateExecution();
1037
1195
  rb_funcall(self, rb_intern("stop_attached"), 0);
1038
1196
 
1039
1197
  return Qnil;
@@ -1047,22 +1205,148 @@ rb_context_dispose(VALUE self) {
1047
1205
 
1048
1206
  free_context(context_info);
1049
1207
 
1050
- if (context_info->isolate_info && context_info->isolate_info->refs_count == 2) {
1051
- // special case, we only have isolate + context so we can burn the
1052
- // isolate as well
1053
- free_isolate(context_info->isolate_info);
1054
- }
1055
1208
  return Qnil;
1056
1209
  }
1057
1210
 
1211
+ static void*
1212
+ nogvl_context_call(void *args) {
1213
+
1214
+ FunctionCall *call = (FunctionCall *) args;
1215
+ if (!call) {
1216
+ return NULL;
1217
+ }
1218
+ Isolate* isolate = call->context_info->isolate_info->isolate;
1219
+
1220
+ // in gvl flag
1221
+ isolate->SetData(IN_GVL, (void*)false);
1222
+ // terminate ASAP
1223
+ isolate->SetData(DO_TERMINATE, (void*)false);
1224
+
1225
+ Isolate::Scope isolate_scope(isolate);
1226
+ EscapableHandleScope handle_scope(isolate);
1227
+ TryCatch trycatch(isolate);
1228
+
1229
+ Local<Context> context = call->context_info->context->Get(isolate);
1230
+ Context::Scope context_scope(context);
1231
+
1232
+ Local<Function> fun = call->fun;
1233
+
1234
+ EvalResult& eval_res = call->result;
1235
+ eval_res.parsed = true;
1236
+
1237
+ MaybeLocal<v8::Value> res = fun->Call(context, context->Global(), call->argc, call->argv);
1238
+ prepare_result(res, trycatch, isolate, context, eval_res);
1239
+
1240
+ isolate->SetData(IN_GVL, (void*)true);
1241
+
1242
+ return NULL;
1243
+ }
1244
+
1245
+ static void unblock_function(void *args) {
1246
+ FunctionCall *call = (FunctionCall *) args;
1247
+ call->context_info->isolate_info->interrupted = true;
1248
+ }
1249
+
1250
+ static VALUE
1251
+ rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1252
+ ContextInfo* context_info;
1253
+ FunctionCall call;
1254
+ VALUE *call_argv = NULL;
1255
+
1256
+ Data_Get_Struct(self, ContextInfo, context_info);
1257
+ Isolate* isolate = context_info->isolate_info->isolate;
1258
+
1259
+ if (argc < 1) {
1260
+ rb_raise(rb_eArgError, "need at least one argument %d", argc);
1261
+ }
1262
+
1263
+ VALUE function_name = argv[0];
1264
+ if (TYPE(function_name) != T_STRING) {
1265
+ rb_raise(rb_eTypeError, "first argument should be a String");
1266
+ }
1267
+
1268
+ char *fname = RSTRING_PTR(function_name);
1269
+ if (!fname) {
1270
+ return Qnil;
1271
+ }
1272
+
1273
+ call.context_info = context_info;
1274
+ call.error = false;
1275
+ call.function_name = fname;
1276
+ call.argc = argc - 1;
1277
+ call.argv = NULL;
1278
+ if (call.argc > 0) {
1279
+ // skip first argument which is the function name
1280
+ call_argv = argv + 1;
1281
+ }
1282
+
1283
+ bool missingFunction = false;
1284
+
1285
+ {
1286
+ Locker lock(isolate);
1287
+ Isolate::Scope isolate_scope(isolate);
1288
+ HandleScope handle_scope(isolate);
1289
+
1290
+ Local<Context> context = context_info->context->Get(isolate);
1291
+ Context::Scope context_scope(context);
1292
+
1293
+ // examples of such usage can be found in
1294
+ // https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
1295
+ Local<String> fname = String::NewFromUtf8(isolate, call.function_name);
1296
+ MaybeLocal<v8::Value> val = context->Global()->Get(fname);
1297
+
1298
+ if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1299
+ missingFunction = true;
1300
+ } else {
1301
+
1302
+ Local<v8::Function> fun = Local<v8::Function>::Cast(val.ToLocalChecked());
1303
+ call.fun = fun;
1304
+ int fun_argc = call.argc;
1305
+
1306
+ if (fun_argc > 0) {
1307
+ call.argv = (v8::Local<Value> *) malloc(sizeof(void *) * fun_argc);
1308
+ if (!call.argv) {
1309
+ return Qnil;
1310
+ }
1311
+ for(int i=0; i < fun_argc; i++) {
1312
+ call.argv[i] = convert_ruby_to_v8(isolate, call_argv[i]);
1313
+ }
1314
+ }
1315
+
1316
+ rb_thread_call_without_gvl(nogvl_context_call, &call, unblock_function, &call);
1317
+ free(call.argv);
1318
+
1319
+ }
1320
+ }
1321
+
1322
+ if (missingFunction) {
1323
+ rb_raise(rb_eScriptRuntimeError, "Unknown JavaScript method invoked");
1324
+ }
1325
+
1326
+ return convert_result_to_ruby(self, call.result);
1327
+ }
1328
+
1329
+ static VALUE rb_context_create_isolate_value(VALUE self) {
1330
+ ContextInfo* context_info;
1331
+ Data_Get_Struct(self, ContextInfo, context_info);
1332
+ IsolateInfo *isolate_info = context_info->isolate_info;
1333
+
1334
+ if (!isolate_info) {
1335
+ return Qnil;
1336
+ }
1337
+
1338
+ isolate_info->hold();
1339
+ return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
1340
+ }
1341
+
1058
1342
  extern "C" {
1059
1343
 
1060
1344
  void Init_mini_racer_extension ( void )
1061
1345
  {
1062
1346
  VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
1063
- VALUE rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1064
- VALUE rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
1065
- VALUE rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
1347
+ rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1348
+ rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
1349
+ rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
1066
1350
  VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
1067
1351
 
1068
1352
  VALUE rb_eError = rb_define_class_under(rb_mMiniRacer, "Error", rb_eStandardError);
@@ -1084,18 +1368,21 @@ extern "C" {
1084
1368
  rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
1085
1369
  rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
1086
1370
  rb_define_method(rb_cContext, "heap_stats", (VALUE(*)(...))&rb_heap_stats, 0);
1371
+ rb_define_private_method(rb_cContext, "create_isolate_value",(VALUE(*)(...))&rb_context_create_isolate_value, 0);
1372
+ rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
1373
+ rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
1374
+ rb_define_private_method(rb_cContext, "isolate_mutex", (VALUE(*)(...))&rb_context_isolate_mutex, 0);
1375
+ rb_define_private_method(rb_cContext, "init_unsafe",(VALUE(*)(...))&rb_context_init_unsafe, 2);
1087
1376
 
1088
1377
  rb_define_alloc_func(rb_cContext, allocate);
1089
1378
  rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
1090
1379
  rb_define_alloc_func(rb_cIsolate, allocate_isolate);
1091
1380
 
1092
- rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
1093
- rb_define_private_method(rb_cContext, "init_with_isolate",(VALUE(*)(...))&rb_context_init_with_isolate, 1);
1094
1381
  rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
1095
1382
  rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
1096
1383
 
1097
1384
  rb_define_method(rb_cSnapshot, "size", (VALUE(*)(...))&rb_snapshot_size, 0);
1098
- rb_define_method(rb_cSnapshot, "warmup!", (VALUE(*)(...))&rb_snapshot_warmup, 1);
1385
+ rb_define_method(rb_cSnapshot, "warmup_unsafe!", (VALUE(*)(...))&rb_snapshot_warmup_unsafe, 1);
1099
1386
  rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1100
1387
 
1101
1388
  rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);