mini_racer 0.1.15 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- 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);