mini_racer 0.1.4 → 0.1.5.pre.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
2
  SHA1:
3
- metadata.gz: 0508a6995a631050ed4470222e522e2794322f2a
4
- data.tar.gz: a42ed52c3df8490ce8e3a5766ecb5389dc813971
3
+ metadata.gz: e83c651e071c15dd06c626097c2e865b4dbf9fe0
4
+ data.tar.gz: 9e39ecff4b86fcd9176bcf198fafa7ca99afcf9e
5
5
  SHA512:
6
- metadata.gz: 963c946ed997001fb18d8f81e2da1a0a17c9554588b693a129a88a830631c5bf8c7a0a222944443ec97efe393d72f6981da0d938c24e1d0a5454a780c5b7d646
7
- data.tar.gz: 26aa65a782780c47bb3e3683ca196e4cd26f2bd03fdf6ceca1862bb8d22f2bdf2477845b5ff096cf28cdb1713fddb35b7a3cbbc1703947772b3dca397bee5852
6
+ metadata.gz: 4c2ad4fb2d10c44ff6018d02d86ae4631b71a6ec95106c6d70329418b6a9f9e0d5b48fba472b5a455c33dca25ed876d57355a867e5b295adcd1716b8645abc68
7
+ data.tar.gz: cca28fca48a330ce45713f59e485301456a2f2e332bec7fbcfc8d539244e6762a8a9210c75cacb5484a200216d37188673f549ab5735775d26e4c39855e60a99
data/.travis.yml CHANGED
@@ -8,7 +8,7 @@ matrix:
8
8
  include:
9
9
  - rvm: 2.2
10
10
  os: osx
11
- osx_image: xcode7.3
11
+ osx_image: xcode8
12
12
  dist: trusty
13
13
  sudo: true
14
14
  before_install:
data/CHANGELOG CHANGED
@@ -1,3 +1,10 @@
1
+ 10-10-2017
2
+
3
+ - 0.1.5
4
+
5
+ - Support for snapshots, shared isolates, runtime flags thanks to @wk8
6
+ - Fix timeout behavior when it occurs in an attached Ruby method
7
+
1
8
  19-05-2016
2
9
 
3
10
  - 0.1.4
data/README.md CHANGED
@@ -8,7 +8,7 @@ MiniRacer provides a minimal two way bridge between the V8 JavaScript engine and
8
8
 
9
9
  It was created as an alternative to the excellent [therubyracer](https://github.com/cowboyd/therubyracer). Unlike therubyracer, mini_racer only implements a minimal bridge. This reduces the surface area making upgrading v8 much simpler and exhaustive testing simpler.
10
10
 
11
- MiniRacer has an adapter for [execjs](https://github.com/sstephenson/execjs) so it can be used directly with Rails projects to minify assets, run babel or compile CoffeeScript.
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
13
  ## Features
14
14
 
@@ -88,6 +88,144 @@ puts context.eval("counter")
88
88
 
89
89
  ```
90
90
 
91
+ ### Snapshots
92
+
93
+ Contexts can be created with pre-loaded snapshots:
94
+
95
+ ```ruby
96
+
97
+ snapshot = MiniRacer::Snapshot.new('function hello() { return "world!"; }')
98
+
99
+ context = MiniRacer::Context.new(snapshot: snapshot)
100
+
101
+ context.eval("hello()")
102
+ # => "world!"
103
+
104
+ ```
105
+
106
+ Snapshots can come in handy for example if you want your contexts to be pre-loaded for effiency. It uses [V8 snapshots](http://v8project.blogspot.com/2015/09/custom-startup-snapshots.html) under the hood; see [this link](http://v8project.blogspot.com/2015/09/custom-startup-snapshots.html) for caveats using these, in particular:
107
+
108
+ ```
109
+ There is an important limitation to snapshots: they can only capture V8’s
110
+ heap. Any interaction from V8 with the outside is off-limits when creating the
111
+ snapshot. Such interactions include:
112
+
113
+ * defining and calling API callbacks (i.e. functions created via v8::FunctionTemplate)
114
+ * creating typed arrays, since the backing store may be allocated outside of V8
115
+
116
+ And of course, values derived from sources such as `Math.random` or `Date.now`
117
+ are fixed once the snapshot has been captured. They are no longer really random
118
+ nor reflect the current time.
119
+ ```
120
+
121
+ Also note that snapshots can be warmed up, using the `warmup!` method, which allows you to call functions which are otherwise lazily compiled to get them to compile right away; any side effect of your warm up code being then dismissed. [More details on warming up here](https://github.com/electron/electron/issues/169#issuecomment-76783481), and a small example:
122
+
123
+ ```ruby
124
+
125
+ snapshot = MiniRacer::Snapshot.new('var counter = 0; function hello() { counter++; return "world! "; }')
126
+
127
+ snapshot.warmup!('hello()')
128
+
129
+ context = MiniRacer::Context.new(snapshot: snapshot)
130
+
131
+ context.eval('hello()')
132
+ # => "world! 1"
133
+ context.eval('counter')
134
+ # => 1
135
+
136
+ ```
137
+
138
+ ### Shared isolates
139
+
140
+ By default, MiniRacer's contexts each have their own isolate (V8 runtime). For efficiency, it is possible to re-use an isolate across contexts:
141
+
142
+ ```ruby
143
+
144
+ isolate = MiniRacer::Isolate.new
145
+
146
+ context1 = MiniRacer::Context.new(isolate: isolate)
147
+ context2 = MiniRacer::Context.new(isolate: isolate)
148
+
149
+ context1.isolate == context2.isolate
150
+ # => true
151
+ ```
152
+
153
+ The main benefit of this is avoiding creating/destroying isolates when not needed (for example if you use a lot of contexts).
154
+
155
+ The caveat with this is that a given isolate can only execute one context at a time, so don't share isolates across contexts that you want to run concurrently.
156
+
157
+ Also, note that if you want to use shared isolates together with snapshots, you need to first create an isolate with that snapshot, and then create contexts from that isolate:
158
+
159
+ ```ruby
160
+ snapshot = MiniRacer::Snapshot.new('function hello() { return "world!"; }')
161
+
162
+ isolate = MiniRacer::Isolate.new(snapshot)
163
+
164
+ context = MiniRacer::Context.new(isolate: isolate)
165
+
166
+ context.eval("hello()")
167
+ # => "world!"
168
+ ```
169
+
170
+ Re-using the same isolate over and over again means V8's garbage collector will have to run to clean it up every now and then; it's possible to trigger a _blocking_ V8 GC run inside your isolate by running the `idle_notification` method on it, which takes a single argument: the amount of time (in milliseconds) that V8 should use at most for garbage collecting:
171
+
172
+ ```ruby
173
+ isolate = MiniRacer::Isolate.new
174
+
175
+ context = MiniRacer::Context.new(isolate: isolate)
176
+
177
+ # do stuff with that context...
178
+
179
+ # give up to 100ms for V8 garbage collection
180
+ isolate.idle_notification(100)
181
+
182
+ ```
183
+
184
+ This can come in handy to force V8 GC runs for example in between requests if you use MiniRacer on a web application.
185
+
186
+ Note that this method maps directly to [`v8::Isolate::IdleNotification`](http://bespin.cz/~ondras/html/classv8_1_1Isolate.html#aea16cbb2e351de9a3ae7be2b7cb48297), and that in particular its return value is the same (true if there is no further garbage to collect, false otherwise) and the same caveats apply, in particular that `there is no guarantee that the [call will return] within the time limit.`
187
+
188
+ ### V8 Runtime flags
189
+
190
+ It is possible to set V8 Runtime flags:
191
+
192
+ ```ruby
193
+ MiniRacer::Platform.set_flags! :noconcurrent_recompilation, max_inlining_levels: 10
194
+ ```
195
+
196
+ This can come in handy if you want to use MiniRacer with Unicorn, which doesn't seem to alwatys appreciate V8's liberal use of threading:
197
+ ```ruby
198
+ MiniRacer::Platform.set_flags! :noconcurrent_recompilation, :noconcurrent_sweeping
199
+ ```
200
+
201
+ Or else to unlock experimental features in V8, for example tail recursion optimization:
202
+ ```ruby
203
+ MiniRacer::Platform.set_flags! :harmony
204
+
205
+ js = <<-JS
206
+ 'use strict';
207
+ var f = function f(n){
208
+ if (n <= 0) {
209
+ return 'foo';
210
+ }
211
+ return f(n - 1);
212
+ }
213
+
214
+ f(1e6);
215
+ JS
216
+
217
+ context = MiniRacer::Context.new
218
+
219
+ context.eval js
220
+ # => "foo"
221
+ ```
222
+ The same code without the harmony runtime flag results in a `MiniRacer::RuntimeError: RangeError: Maximum call stack size exceeded` exception.
223
+ Please refer to http://node.green/ as a reference on other harmony features.
224
+
225
+ A list of all V8 runtime flags can be found using `node --v8-options`, or else by perusing [the V8 source code for flags (make sure to use the right version of V8)](https://github.com/v8/v8/blob/master/src/flag-definitions.h).
226
+
227
+ Note that runtime flags must be set before any other operation (e.g. creating a context, a snapshot or an isolate), otherwise an exception will be thrown.
228
+
91
229
  ## Performance
92
230
 
93
231
  The `bench` folder contains benchmark.
@@ -17,6 +17,28 @@ if ENV['CXX']
17
17
  CONFIG['CXX'] = ENV['CXX']
18
18
  end
19
19
 
20
+ CXX11_TEST = <<EOS
21
+ #if __cplusplus <= 199711L
22
+ # error A compiler that supports at least C++11 is required in order to compile this project.
23
+ #endif
24
+ EOS
25
+
26
+ `echo "#{CXX11_TEST}" | #{CONFIG['CXX']} -std=c++0x -x c++ -E -`
27
+ unless $?.success?
28
+ warn <<EOS
29
+
30
+
31
+ WARNING: C++11 support is required for compiling mini_racer. Please make sure
32
+ you are using a compiler that supports at least C++11. Examples of such
33
+ compilers are GCC 4.7+ and Clang 3.2+.
34
+
35
+ If you are using Travis, consider either migrating your bulid to Ubuntu Trusty or
36
+ installing GCC 4.8. See mini_racer's README.md for more information.
37
+
38
+
39
+ EOS
40
+ end
41
+
20
42
  CONFIG['LDSHARED'] = '$(CXX) -shared' unless RUBY_PLATFORM =~ /darwin/
21
43
  if CONFIG['warnflags']
22
44
  CONFIG['warnflags'].gsub!('-Wdeclaration-after-statement', '')
@@ -1,11 +1,12 @@
1
1
  #include <stdio.h>
2
2
  #include <ruby.h>
3
3
  #include <ruby/thread.h>
4
- #include <include/v8.h>
5
- #include <include/libplatform/libplatform.h>
4
+ #include <v8.h>
5
+ #include <libplatform/libplatform.h>
6
6
  #include <ruby/encoding.h>
7
7
  #include <pthread.h>
8
8
  #include <unistd.h>
9
+ #include <mutex>
9
10
 
10
11
  using namespace v8;
11
12
 
@@ -19,11 +20,28 @@ class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
19
20
  virtual void Free(void* data, size_t) { free(data); }
20
21
  };
21
22
 
23
+ typedef struct {
24
+ const char* data;
25
+ int raw_size;
26
+ } SnapshotInfo;
27
+
22
28
  typedef struct {
23
29
  Isolate* isolate;
24
- Persistent<Context>* context;
25
30
  ArrayBufferAllocator* allocator;
31
+ StartupData* startup_data;
26
32
  bool interrupted;
33
+
34
+ // how many references to this isolate exist
35
+ // we can't rely on Ruby's GC for this, because when destroying
36
+ // objects, Ruby will destroy ruby objects first, then call the
37
+ // extenstion's deallocators. In this case, that means it would
38
+ // call `deallocate_isolate` _before_ `deallocate`, causing a segfault
39
+ int refs_count;
40
+ } IsolateInfo;
41
+
42
+ typedef struct {
43
+ IsolateInfo* isolate_info;
44
+ Persistent<Context>* context;
27
45
  } ContextInfo;
28
46
 
29
47
  typedef struct {
@@ -46,42 +64,71 @@ static VALUE rb_eScriptTerminatedError;
46
64
  static VALUE rb_eParseError;
47
65
  static VALUE rb_eScriptRuntimeError;
48
66
  static VALUE rb_cJavaScriptFunction;
67
+ static VALUE rb_eSnapshotError;
68
+ static VALUE rb_ePlatformAlreadyInitializedError;
49
69
 
70
+ static VALUE rb_cFailedV8Conversion;
50
71
  static VALUE rb_cDateTime = Qnil;
51
72
 
52
73
  static Platform* current_platform = NULL;
74
+ static std::mutex platform_lock;
75
+
76
+ static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
77
+ bool platform_already_initialized = false;
78
+
79
+ platform_lock.lock();
53
80
 
54
- static void init_v8() {
55
81
  if (current_platform == NULL) {
56
- V8::InitializeICU();
57
- current_platform = platform::CreateDefaultPlatform();
58
- V8::InitializePlatform(current_platform);
59
- V8::Initialize();
82
+ V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
83
+ } else {
84
+ platform_already_initialized = true;
60
85
  }
86
+
87
+ platform_lock.unlock();
88
+
89
+ // important to raise outside of the lock
90
+ if (platform_already_initialized) {
91
+ rb_raise(rb_ePlatformAlreadyInitializedError, "The V8 platform is already initialized");
92
+ }
93
+
94
+ return Qnil;
61
95
  }
62
96
 
63
- void* breaker(void *d) {
64
- EvalParams* data = (EvalParams*)d;
65
- usleep(data->timeout*1000);
66
- pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
67
- V8::TerminateExecution(data->context_info->isolate);
68
- return NULL;
97
+ static void init_v8() {
98
+ // no need to wait for the lock if already initialized
99
+ if (current_platform != NULL) return;
100
+
101
+ platform_lock.lock();
102
+
103
+ if (current_platform == NULL) {
104
+ V8::InitializeICU();
105
+ current_platform = platform::CreateDefaultPlatform();
106
+ V8::InitializePlatform(current_platform);
107
+ V8::Initialize();
108
+ }
109
+
110
+ platform_lock.unlock();
69
111
  }
70
112
 
71
113
  void*
72
114
  nogvl_context_eval(void* arg) {
115
+
73
116
  EvalParams* eval_params = (EvalParams*)arg;
74
117
  EvalResult* result = eval_params->result;
75
- Isolate* isolate = eval_params->context_info->isolate;
118
+ Isolate* isolate = eval_params->context_info->isolate_info->isolate;
76
119
  Isolate::Scope isolate_scope(isolate);
77
120
  HandleScope handle_scope(isolate);
78
121
 
79
122
  TryCatch trycatch(isolate);
80
123
 
81
124
  Local<Context> context = eval_params->context_info->context->Get(isolate);
82
-
83
125
  Context::Scope context_scope(context);
84
126
 
127
+ // in gvl flag
128
+ isolate->SetData(0, (void*)false);
129
+ // terminate ASAP
130
+ isolate->SetData(1, (void*)false);
131
+
85
132
  MaybeLocal<Script> parsed_script = Script::Compile(context, *eval_params->eval);
86
133
  result->parsed = !parsed_script.IsEmpty();
87
134
  result->executed = false;
@@ -93,19 +140,8 @@ nogvl_context_eval(void* arg) {
93
140
  result->message->Reset(isolate, trycatch.Exception());
94
141
  } else {
95
142
 
96
- pthread_t breaker_thread;
97
-
98
- if (eval_params->timeout > 0) {
99
- pthread_create(&breaker_thread, NULL, breaker, (void*)eval_params);
100
- }
101
-
102
143
  MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
103
144
 
104
- if (eval_params->timeout > 0) {
105
- pthread_cancel(breaker_thread);
106
- pthread_join(breaker_thread, NULL);
107
- }
108
-
109
145
  result->executed = !maybe_value.IsEmpty();
110
146
 
111
147
  if (result->executed) {
@@ -130,6 +166,8 @@ nogvl_context_eval(void* arg) {
130
166
  Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, (int)len).ToLocalChecked();
131
167
  result->message->Reset(isolate, v8_message);
132
168
  } else if(trycatch.HasTerminated()) {
169
+
170
+
133
171
  result->terminated = true;
134
172
  result->message = new Persistent<Value>();
135
173
  Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
@@ -142,6 +180,9 @@ nogvl_context_eval(void* arg) {
142
180
  }
143
181
  }
144
182
 
183
+ isolate->SetData(0, (void*)true);
184
+
185
+
145
186
  return NULL;
146
187
  }
147
188
 
@@ -175,6 +216,9 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
175
216
  for(uint32_t i=0; i < arr->Length(); i++) {
176
217
  Local<Value> element = arr->Get(i);
177
218
  VALUE rb_elem = convert_v8_to_ruby(isolate, element);
219
+ if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
220
+ return rb_elem;
221
+ }
178
222
  rb_ary_push(rb_array, rb_elem);
179
223
  }
180
224
  return rb_array;
@@ -193,6 +237,9 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
193
237
  }
194
238
 
195
239
  if (value->IsObject()) {
240
+
241
+ TryCatch trycatch(isolate);
242
+
196
243
  VALUE rb_hash = rb_hash_new();
197
244
  Local<Context> context = Context::New(isolate);
198
245
  Local<Object> object = value->ToObject();
@@ -203,6 +250,14 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Handle<Value> &value) {
203
250
  Local<Value> key = props->Get(i);
204
251
  VALUE rb_key = convert_v8_to_ruby(isolate, key);
205
252
  Local<Value> value = object->Get(key);
253
+ // this may have failed due to Get raising
254
+
255
+ if (trycatch.HasCaught()) {
256
+ // TODO isolate code that translates execption to ruby
257
+ // exception so we can properly return it
258
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
259
+ }
260
+
206
261
  VALUE rb_value = convert_v8_to_ruby(isolate, value);
207
262
  rb_hash_aset(rb_hash, rb_key, rb_value);
208
263
  }
@@ -233,7 +288,6 @@ static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
233
288
  {
234
289
  return scope.Escape(Number::New(isolate, (double)fixnum));
235
290
  }
236
-
237
291
  return scope.Escape(Integer::New(isolate, (int)fixnum));
238
292
  case T_FLOAT:
239
293
  return scope.Escape(Number::New(isolate, NUM2DBL(value)));
@@ -273,7 +327,6 @@ static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
273
327
  {
274
328
  value = rb_funcall(value, rb_intern("to_time"), 0);
275
329
  }
276
-
277
330
  value = rb_funcall(value, rb_intern("to_f"), 0);
278
331
  return scope.Escape(Date::New(isolate, NUM2DBL(value) * 1000));
279
332
  }
@@ -296,9 +349,127 @@ static Handle<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value) {
296
349
 
297
350
  static void unblock_eval(void *ptr) {
298
351
  EvalParams* eval = (EvalParams*)ptr;
299
- eval->context_info->interrupted = true;
352
+ eval->context_info->isolate_info->interrupted = true;
300
353
  }
301
354
 
355
+ static VALUE rb_snapshot_size(VALUE self, VALUE str) {
356
+ SnapshotInfo* snapshot_info;
357
+ Data_Get_Struct(self, SnapshotInfo, snapshot_info);
358
+
359
+ return INT2NUM(snapshot_info->raw_size);
360
+ }
361
+
362
+ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
363
+ SnapshotInfo* snapshot_info;
364
+ Data_Get_Struct(self, SnapshotInfo, snapshot_info);
365
+
366
+ init_v8();
367
+
368
+ StartupData startup_data = V8::CreateSnapshotDataBlob(RSTRING_PTR(str));
369
+
370
+ if (startup_data.data == NULL && startup_data.raw_size == 0) {
371
+ rb_raise(rb_eSnapshotError, "Could not create snapshot, most likely the source is incorrect");
372
+ }
373
+
374
+ snapshot_info->data = startup_data.data;
375
+ snapshot_info->raw_size = startup_data.raw_size;
376
+
377
+ return Qnil;
378
+ }
379
+
380
+ static VALUE rb_snapshot_warmup(VALUE self, VALUE str) {
381
+ SnapshotInfo* snapshot_info;
382
+ Data_Get_Struct(self, SnapshotInfo, snapshot_info);
383
+
384
+ init_v8();
385
+
386
+ StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
387
+ StartupData warm_startup_data = V8::WarmUpSnapshotDataBlob(cold_startup_data, RSTRING_PTR(str));
388
+
389
+ if (warm_startup_data.data == NULL && warm_startup_data.raw_size == 0) {
390
+ rb_raise(rb_eSnapshotError, "Could not warm up snapshot, most likely the source is incorrect");
391
+ } else {
392
+ delete[] snapshot_info->data;
393
+
394
+ snapshot_info->data = warm_startup_data.data;
395
+ snapshot_info->raw_size = warm_startup_data.raw_size;
396
+ }
397
+
398
+ return self;
399
+ }
400
+
401
+ static VALUE rb_isolate_init_with_snapshot(VALUE self, VALUE snapshot) {
402
+ IsolateInfo* isolate_info;
403
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
404
+
405
+ init_v8();
406
+
407
+ isolate_info->allocator = new ArrayBufferAllocator();
408
+ isolate_info->interrupted = false;
409
+ isolate_info->refs_count = 1;
410
+
411
+ Isolate::CreateParams create_params;
412
+ create_params.array_buffer_allocator = isolate_info->allocator;
413
+
414
+ StartupData* startup_data = NULL;
415
+ if (!NIL_P(snapshot)) {
416
+ SnapshotInfo* snapshot_info;
417
+ Data_Get_Struct(snapshot, SnapshotInfo, snapshot_info);
418
+
419
+ int raw_size = snapshot_info->raw_size;
420
+ char* data = new char[raw_size];
421
+ memcpy(data, snapshot_info->data, sizeof(char) * raw_size);
422
+
423
+ startup_data = new StartupData;
424
+ startup_data->data = data;
425
+ startup_data->raw_size = raw_size;
426
+
427
+ create_params.snapshot_blob = startup_data;
428
+ }
429
+
430
+ isolate_info->startup_data = startup_data;
431
+ isolate_info->isolate = Isolate::New(create_params);
432
+
433
+ return Qnil;
434
+ }
435
+
436
+ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
437
+ IsolateInfo* isolate_info;
438
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
439
+
440
+ return isolate_info->isolate->IdleNotification(NUM2INT(idle_time_in_ms)) ? Qtrue : Qfalse;
441
+ }
442
+
443
+ static VALUE rb_context_init_with_isolate(VALUE self, VALUE isolate) {
444
+ ContextInfo* context_info;
445
+ Data_Get_Struct(self, ContextInfo, context_info);
446
+
447
+ init_v8();
448
+
449
+ IsolateInfo* isolate_info;
450
+ Data_Get_Struct(isolate, IsolateInfo, isolate_info);
451
+
452
+ context_info->isolate_info = isolate_info;
453
+ isolate_info->refs_count++;
454
+
455
+ {
456
+ Locker lock(isolate_info->isolate);
457
+ Isolate::Scope isolate_scope(isolate_info->isolate);
458
+ HandleScope handle_scope(isolate_info->isolate);
459
+
460
+ Local<Context> context = Context::New(isolate_info->isolate);
461
+
462
+ context_info->context = new Persistent<Context>();
463
+ context_info->context->Reset(isolate_info->isolate, context);
464
+ }
465
+
466
+ if (Qnil == rb_cDateTime && rb_funcall(rb_cObject, rb_intern("const_defined?"), 1, rb_str_new2("DateTime")) == Qtrue)
467
+ {
468
+ rb_cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
469
+ }
470
+
471
+ return Qnil;
472
+ }
302
473
 
303
474
  static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
304
475
 
@@ -311,13 +482,16 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
311
482
  VALUE backtrace = Qnil;
312
483
 
313
484
  Data_Get_Struct(self, ContextInfo, context_info);
485
+ Isolate* isolate = context_info->isolate_info->isolate;
486
+
487
+
314
488
 
315
489
  {
316
- Locker lock(context_info->isolate);
317
- Isolate::Scope isolate_scope(context_info->isolate);
318
- HandleScope handle_scope(context_info->isolate);
490
+ Locker lock(isolate);
491
+ Isolate::Scope isolate_scope(isolate);
492
+ HandleScope handle_scope(isolate);
319
493
 
320
- Local<String> eval = String::NewFromUtf8(context_info->isolate, RSTRING_PTR(str),
494
+ Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(str),
321
495
  NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
322
496
 
323
497
  eval_params.context_info = context_info;
@@ -335,15 +509,15 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
335
509
  rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
336
510
 
337
511
  if (eval_result.message != NULL) {
338
- Local<Value> tmp = Local<Value>::New(context_info->isolate, *eval_result.message);
339
- message = convert_v8_to_ruby(context_info->isolate, tmp);
512
+ Local<Value> tmp = Local<Value>::New(isolate, *eval_result.message);
513
+ message = convert_v8_to_ruby(isolate, tmp);
340
514
  eval_result.message->Reset();
341
515
  delete eval_result.message;
342
516
  }
343
517
 
344
518
  if (eval_result.backtrace != NULL) {
345
- Local<Value> tmp = Local<Value>::New(context_info->isolate, *eval_result.backtrace);
346
- backtrace = convert_v8_to_ruby(context_info->isolate, tmp);
519
+ Local<Value> tmp = Local<Value>::New(isolate, *eval_result.backtrace);
520
+ backtrace = convert_v8_to_ruby(isolate, tmp);
347
521
  eval_result.backtrace->Reset();
348
522
  delete eval_result.backtrace;
349
523
  }
@@ -377,19 +551,25 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str) {
377
551
  }
378
552
  }
379
553
 
380
- // New scope for return value
554
+ // New scope for return value, must release GVL which
381
555
  {
382
- Locker lock(context_info->isolate);
383
- Isolate::Scope isolate_scope(context_info->isolate);
384
- HandleScope handle_scope(context_info->isolate);
556
+ Locker lock(isolate);
557
+ Isolate::Scope isolate_scope(isolate);
558
+ HandleScope handle_scope(isolate);
385
559
 
386
- Local<Value> tmp = Local<Value>::New(context_info->isolate, *eval_result.value);
387
- result = convert_v8_to_ruby(context_info->isolate, tmp);
560
+ Local<Value> tmp = Local<Value>::New(isolate, *eval_result.value);
561
+ result = convert_v8_to_ruby(isolate, tmp);
388
562
 
389
563
  eval_result.value->Reset();
390
564
  delete eval_result.value;
391
565
  }
392
566
 
567
+ if (rb_funcall(result, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
568
+ // TODO try to recover stack trace from the conversion error
569
+ rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
570
+ }
571
+
572
+
393
573
  return result;
394
574
  }
395
575
 
@@ -424,7 +604,7 @@ void*
424
604
  gvl_ruby_callback(void* data) {
425
605
 
426
606
  FunctionCallbackInfo<Value>* args = (FunctionCallbackInfo<Value>*)data;
427
- VALUE* ruby_args;
607
+ VALUE* ruby_args = NULL;
428
608
  int length = args->Length();
429
609
  VALUE callback;
430
610
  VALUE result;
@@ -456,8 +636,14 @@ gvl_ruby_callback(void* data) {
456
636
  callback_data.args = ruby_args;
457
637
  callback_data.failed = false;
458
638
 
459
- result = rb_rescue((VALUE(*)(...))&protected_callback, (VALUE)(&callback_data),
460
- (VALUE(*)(...))&rescue_callback, (VALUE)(&callback_data));
639
+ if ((bool)args->GetIsolate()->GetData(1) == true) {
640
+ args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Terminated execution during tansition from Ruby to JS"));
641
+ V8::TerminateExecution(args->GetIsolate());
642
+ return NULL;
643
+ }
644
+
645
+ result = rb_rescue2((VALUE(*)(...))&protected_callback, (VALUE)(&callback_data),
646
+ (VALUE(*)(...))&rescue_callback, (VALUE)(&callback_data), rb_eException, (VALUE)0);
461
647
 
462
648
  if(callback_data.failed) {
463
649
  VALUE parent = rb_iv_get(self, "@parent");
@@ -474,11 +660,23 @@ gvl_ruby_callback(void* data) {
474
660
  xfree(ruby_args);
475
661
  }
476
662
 
663
+ if ((bool)args->GetIsolate()->GetData(1) == true) {
664
+ Isolate* isolate = args->GetIsolate();
665
+ V8::TerminateExecution(isolate);
666
+ }
667
+
477
668
  return NULL;
478
669
  }
479
670
 
480
671
  static void ruby_callback(const FunctionCallbackInfo<Value>& args) {
481
- rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
672
+
673
+ bool has_gvl = (bool)args.GetIsolate()->GetData(0);
674
+
675
+ if(has_gvl) {
676
+ gvl_ruby_callback((void*)&args);
677
+ } else {
678
+ rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
679
+ }
482
680
  }
483
681
 
484
682
 
@@ -495,16 +693,17 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
495
693
  bool attach_error = false;
496
694
 
497
695
  Data_Get_Struct(parent, ContextInfo, context_info);
696
+ Isolate* isolate = context_info->isolate_info->isolate;
498
697
 
499
698
  {
500
- Locker lock(context_info->isolate);
501
- Isolate::Scope isolate_scope(context_info->isolate);
502
- HandleScope handle_scope(context_info->isolate);
699
+ Locker lock(isolate);
700
+ Isolate::Scope isolate_scope(isolate);
701
+ HandleScope handle_scope(isolate);
503
702
 
504
- Local<Context> context = context_info->context->Get(context_info->isolate);
703
+ Local<Context> context = context_info->context->Get(isolate);
505
704
  Context::Scope context_scope(context);
506
705
 
507
- Local<String> v8_str = String::NewFromUtf8(context_info->isolate, RSTRING_PTR(name),
706
+ Local<String> v8_str = String::NewFromUtf8(isolate, RSTRING_PTR(name),
508
707
  NewStringType::kNormal, (int)RSTRING_LEN(name)).ToLocalChecked();
509
708
 
510
709
  // copy self so we can access from v8 external
@@ -512,13 +711,13 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
512
711
  Data_Get_Struct(self, VALUE, self_copy);
513
712
  *self_copy = self;
514
713
 
515
- Local<Value> external = External::New(context_info->isolate, self_copy);
714
+ Local<Value> external = External::New(isolate, self_copy);
516
715
 
517
716
  if (parent_object == Qnil) {
518
- context->Global()->Set(v8_str, FunctionTemplate::New(context_info->isolate, ruby_callback, external)->GetFunction());
717
+ context->Global()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
519
718
  } else {
520
719
 
521
- Local<String> eval = String::NewFromUtf8(context_info->isolate, RSTRING_PTR(parent_object_eval),
720
+ Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
522
721
  NewStringType::kNormal, (int)RSTRING_LEN(parent_object_eval)).ToLocalChecked();
523
722
 
524
723
  MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
@@ -531,7 +730,7 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
531
730
  if (!maybe_value.IsEmpty()) {
532
731
  Local<Value> value = maybe_value.ToLocalChecked();
533
732
  if (value->IsObject()){
534
- value.As<Object>()->Set(v8_str, FunctionTemplate::New(context_info->isolate, ruby_callback, external)->GetFunction());
733
+ value.As<Object>()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
535
734
  attach_error = false;
536
735
  }
537
736
  }
@@ -551,71 +750,120 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
551
750
  return Qnil;
552
751
  }
553
752
 
554
- void deallocate(void * data) {
555
- ContextInfo* context_info = (ContextInfo*)data;
556
- {
557
- Locker lock(context_info->isolate);
753
+ void maybe_free_isolate_info(IsolateInfo* isolate_info) {
754
+ // an isolate can only be freed if no Isolate or Context (ruby) object
755
+ // still need it
756
+ if (isolate_info == NULL || isolate_info->refs_count > 0) {
757
+ return;
558
758
  }
559
759
 
560
- {
561
- context_info->context->Reset();
562
- delete context_info->context;
760
+ if (isolate_info->isolate) {
761
+ Locker lock(isolate_info->isolate);
563
762
  }
564
763
 
565
- {
566
- if (context_info->interrupted) {
567
- fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, it can not be disposed and memory will not be reclaimed till the Ruby process exits.");
568
- } else {
569
- context_info->isolate->Dispose();
570
- }
764
+ if (isolate_info->isolate) {
765
+ if (isolate_info->interrupted) {
766
+ fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, it can not be disposed and memory will not be reclaimed till the Ruby process exits.");
767
+ } else {
768
+
769
+ isolate_info->isolate->Dispose();
770
+ }
771
+ isolate_info->isolate = NULL;
772
+ }
773
+
774
+ if (isolate_info->startup_data) {
775
+ delete[] isolate_info->startup_data->data;
776
+ delete isolate_info->startup_data;
777
+ }
778
+
779
+ delete isolate_info->allocator;
780
+ xfree(isolate_info);
781
+ }
782
+
783
+ void deallocate_isolate(void* data) {
784
+ IsolateInfo* isolate_info = (IsolateInfo*) data;
785
+
786
+ isolate_info->refs_count--;
787
+
788
+ maybe_free_isolate_info(isolate_info);
789
+ }
790
+
791
+ void deallocate(void* data) {
792
+ ContextInfo* context_info = (ContextInfo*)data;
793
+ IsolateInfo* isolate_info = context_info->isolate_info;
794
+
795
+ if (context_info->context && isolate_info && isolate_info->isolate) {
796
+ Locker lock(isolate_info->isolate);
797
+ v8::Isolate::Scope isolate_scope(isolate_info->isolate);
798
+ context_info->context->Reset();
799
+ delete context_info->context;
571
800
  }
572
801
 
573
- delete context_info->allocator;
574
- xfree(context_info);
802
+ if (isolate_info) {
803
+ isolate_info->refs_count--;
804
+ maybe_free_isolate_info(isolate_info);
805
+ }
575
806
  }
576
807
 
577
808
  void deallocate_external_function(void * data) {
578
809
  xfree(data);
579
810
  }
580
811
 
812
+ void deallocate_snapshot(void * data) {
813
+ SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
814
+
815
+ delete[] snapshot_info->data;
816
+
817
+ xfree(snapshot_info);
818
+ }
819
+
581
820
  VALUE allocate_external_function(VALUE klass) {
582
821
  VALUE* self = ALLOC(VALUE);
583
822
  return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
584
823
  }
585
824
 
586
825
  VALUE allocate(VALUE klass) {
587
- init_v8();
588
-
589
826
  ContextInfo* context_info = ALLOC(ContextInfo);
590
- context_info->allocator = new ArrayBufferAllocator();
591
- context_info->interrupted = false;
592
- Isolate::CreateParams create_params;
593
- create_params.array_buffer_allocator = context_info->allocator;
827
+ context_info->isolate_info = NULL;
828
+ context_info->context = NULL;
594
829
 
595
- context_info->isolate = Isolate::New(create_params);
830
+ return Data_Wrap_Struct(klass, NULL, deallocate, (void*)context_info);
831
+ }
596
832
 
597
- Locker lock(context_info->isolate);
598
- Isolate::Scope isolate_scope(context_info->isolate);
599
- HandleScope handle_scope(context_info->isolate);
833
+ VALUE allocate_snapshot(VALUE klass) {
834
+ SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
835
+ snapshot_info->data = NULL;
836
+ snapshot_info->raw_size = 0;
600
837
 
601
- Local<Context> context = Context::New(context_info->isolate);
838
+ return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
839
+ }
602
840
 
603
- context_info->context = new Persistent<Context>();
604
- context_info->context->Reset(context_info->isolate, context);
841
+ VALUE allocate_isolate(VALUE klass) {
842
+ IsolateInfo* isolate_info = ALLOC(IsolateInfo);
605
843
 
606
- if (Qnil == rb_cDateTime && rb_funcall(rb_cObject, rb_intern("const_defined?"), 1, rb_str_new2("DateTime")) == Qtrue)
607
- {
608
- rb_cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
609
- }
844
+ isolate_info->isolate = NULL;
845
+ isolate_info->allocator = NULL;
846
+ isolate_info->startup_data = NULL;
847
+ isolate_info->interrupted = false;
848
+ isolate_info->refs_count = 0;
610
849
 
611
- return Data_Wrap_Struct(klass, NULL, deallocate, (void*)context_info);
850
+ return Data_Wrap_Struct(klass, NULL, deallocate_isolate, (void*)isolate_info);
612
851
  }
613
852
 
614
853
  static VALUE
615
854
  rb_context_stop(VALUE self) {
855
+
616
856
  ContextInfo* context_info;
617
857
  Data_Get_Struct(self, ContextInfo, context_info);
618
- V8::TerminateExecution(context_info->isolate);
858
+
859
+ Isolate* isolate = context_info->isolate_info->isolate;
860
+
861
+ // flag for termination
862
+ isolate->SetData(1, (void*)true);
863
+
864
+ V8::TerminateExecution(isolate);
865
+ rb_funcall(self, rb_intern("stop_attached"), 0);
866
+
619
867
  return Qnil;
620
868
  }
621
869
 
@@ -625,20 +873,38 @@ extern "C" {
625
873
  {
626
874
  VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
627
875
  VALUE rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
876
+ VALUE rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
877
+ VALUE rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
878
+ VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
628
879
 
629
880
  VALUE rb_eEvalError = rb_define_class_under(rb_mMiniRacer, "EvalError", rb_eStandardError);
630
881
  rb_eScriptTerminatedError = rb_define_class_under(rb_mMiniRacer, "ScriptTerminatedError", rb_eEvalError);
631
882
  rb_eParseError = rb_define_class_under(rb_mMiniRacer, "ParseError", rb_eEvalError);
632
883
  rb_eScriptRuntimeError = rb_define_class_under(rb_mMiniRacer, "RuntimeError", rb_eEvalError);
633
884
  rb_cJavaScriptFunction = rb_define_class_under(rb_mMiniRacer, "JavaScriptFunction", rb_cObject);
885
+ rb_eSnapshotError = rb_define_class_under(rb_mMiniRacer, "SnapshotError", rb_eStandardError);
886
+ rb_ePlatformAlreadyInitializedError = rb_define_class_under(rb_mMiniRacer, "PlatformAlreadyInitialized", rb_eStandardError);
887
+ rb_cFailedV8Conversion = rb_define_class_under(rb_mMiniRacer, "FailedV8Conversion", rb_cObject);
634
888
 
635
889
  VALUE rb_cExternalFunction = rb_define_class_under(rb_cContext, "ExternalFunction", rb_cObject);
636
890
  rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
637
891
  rb_define_alloc_func(rb_cContext, allocate);
892
+ rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
893
+ rb_define_alloc_func(rb_cIsolate, allocate_isolate);
638
894
 
639
895
  rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 1);
896
+ rb_define_private_method(rb_cContext, "init_with_isolate",(VALUE(*)(...))&rb_context_init_with_isolate, 1);
640
897
  rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
641
898
  rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
899
+
900
+ rb_define_method(rb_cSnapshot, "size", (VALUE(*)(...))&rb_snapshot_size, 0);
901
+ rb_define_method(rb_cSnapshot, "warmup!", (VALUE(*)(...))&rb_snapshot_warmup, 1);
902
+ rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
903
+
904
+ rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
905
+ rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
906
+
907
+ rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
642
908
  }
643
909
 
644
910
  }
data/lib/mini_racer.rb CHANGED
@@ -7,6 +7,15 @@ module MiniRacer
7
7
  class EvalError < StandardError; end
8
8
  class ScriptTerminatedError < EvalError; end
9
9
  class ParseError < EvalError; end
10
+ class SnapshotError < StandardError; end
11
+ class PlatformAlreadyInitialized < StandardError; end
12
+
13
+ class FailedV8Conversion
14
+ attr_reader :info
15
+ def initialize(info)
16
+ @info = info
17
+ end
18
+ end
10
19
 
11
20
  class RuntimeError < EvalError
12
21
  def initialize(message)
@@ -38,6 +47,53 @@ module MiniRacer
38
47
  end
39
48
  end
40
49
 
50
+ class Isolate
51
+ def initialize(snapshot = nil)
52
+ unless snapshot.nil? || snapshot.is_a?(Snapshot)
53
+ raise ArgumentError, "snapshot must be a Snapshot object, passed a #{snapshot.inspect}"
54
+ end
55
+
56
+ @lock = Mutex.new
57
+
58
+ # defined in the C class
59
+ init_with_snapshot(snapshot)
60
+ end
61
+
62
+ def with_lock
63
+ @lock.synchronize { yield }
64
+ end
65
+ end
66
+
67
+ class Platform
68
+ class << self
69
+ def set_flags!(*args, **kwargs)
70
+ flags_to_strings([args, kwargs]).each do |flag|
71
+ # defined in the C class
72
+ set_flag_as_str!(flag)
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def flags_to_strings(flags)
79
+ flags.flatten.map { |flag| flag_to_string(flag) }.flatten
80
+ end
81
+
82
+ # normalize flags to strings, and adds leading dashes if needed
83
+ def flag_to_string(flag)
84
+ if flag.is_a?(Hash)
85
+ flag.map do |key, value|
86
+ "#{flag_to_string(key)} #{value}"
87
+ end
88
+ else
89
+ str = flag.to_s
90
+ str = "--#{str}" unless str.start_with?('--')
91
+ str
92
+ end
93
+ end
94
+ end
95
+ end
96
+
41
97
  # eval is defined in the C class
42
98
  class Context
43
99
 
@@ -74,14 +130,27 @@ module MiniRacer
74
130
  end
75
131
  end
76
132
 
133
+ attr_reader :isolate
134
+
77
135
  def initialize(options = nil)
136
+ options ||= {}
137
+
138
+ check_init_options!(options)
139
+
78
140
  @functions = {}
79
- @lock = Mutex.new
80
141
  @timeout = nil
81
142
  @current_exception = nil
143
+ @timeout = options[:timeout]
144
+ @isolate = options[:isolate] || Isolate.new(options[:snapshot])
145
+
146
+ @callback_mutex = Mutex.new
147
+ @callback_running = false
148
+ @thread_raise_called = false
149
+ @eval_thread = nil
82
150
 
83
- if options
84
- @timeout = options[:timeout]
151
+ isolate.with_lock do
152
+ # defined in the C class
153
+ init_with_isolate(@isolate)
85
154
  end
86
155
  end
87
156
 
@@ -91,19 +160,102 @@ module MiniRacer
91
160
  end
92
161
 
93
162
  def eval(str)
94
- @lock.synchronize do
163
+ @eval_thread = Thread.current
164
+ isolate.with_lock do
95
165
  @current_exception = nil
96
- eval_unsafe(str)
166
+ timeout do
167
+ eval_unsafe(str)
168
+ end
97
169
  end
170
+ ensure
171
+ @eval_thread = nil
98
172
  end
99
173
 
174
+
100
175
  def attach(name, callback)
101
- @lock.synchronize do
102
- external = ExternalFunction.new(name, callback, self)
176
+
177
+ wrapped = lambda do |*args|
178
+ begin
179
+ @callback_mutex.synchronize{
180
+ @callback_running = true
181
+ }
182
+
183
+ callback.call(*args)
184
+ ensure
185
+ @callback_mutex.synchronize {
186
+ @callback_running = false
187
+
188
+ # this is some odd code, but it is required
189
+ # if we raised on this thread we better wait for it
190
+ # otherwise we may end up raising in an unsafe spot
191
+ if @thread_raise_called
192
+ sleep 0.1
193
+ end
194
+ @thread_raise_called = false
195
+ }
196
+ end
197
+ end
198
+
199
+ isolate.with_lock do
200
+ external = ExternalFunction.new(name, wrapped, self)
103
201
  @functions["#{name}"] = external
104
202
  end
105
203
  end
106
204
 
205
+ private
206
+
207
+ def stop_attached
208
+ @callback_mutex.synchronize{
209
+ if @callback_running
210
+ @eval_thread.raise ScriptTerminatedError, "Terminated during callback"
211
+ @thread_raise_called = true
212
+ end
213
+ }
214
+ end
215
+
216
+ def timeout(&blk)
217
+ return blk.call unless @timeout
218
+
219
+ _,wp = IO.pipe
220
+
221
+ Thread.new do
222
+ begin
223
+ result = IO.select([wp],[],[],(@timeout/1000.0))
224
+ if !result
225
+ stop
226
+ end
227
+ rescue
228
+ STDERR.puts "FAILED TO TERMINATE DUE TO TIMEOUT"
229
+ end
230
+ end
231
+
232
+ rval = blk.call
233
+ wp.write("done")
234
+
235
+ rval
236
+ end
237
+
238
+ def check_init_options!(options)
239
+ assert_option_is_nil_or_a('isolate', options[:isolate], Isolate)
240
+ assert_option_is_nil_or_a('snapshot', options[:snapshot], Snapshot)
241
+
242
+ if options[:isolate] && options[:snapshot]
243
+ raise ArgumentError, 'can only pass one of isolate and snapshot options'
244
+ end
245
+ end
246
+
247
+ def assert_option_is_nil_or_a(option_name, object, klass)
248
+ unless object.nil? || object.is_a?(klass)
249
+ raise ArgumentError, "#{option_name} must be a #{klass} object, passed a #{object.inspect}"
250
+ end
251
+ end
107
252
  end
108
253
 
254
+ # `size` and `warmup!` public methods are defined in the C class
255
+ class Snapshot
256
+ def initialize(str = '')
257
+ # defined in the C class
258
+ load(str)
259
+ end
260
+ end
109
261
  end
@@ -1,3 +1,3 @@
1
1
  module MiniRacer
2
- VERSION = "0.1.4"
2
+ VERSION = "0.1.5.pre.0"
3
3
  end
data/mini_racer.gemspec CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency "minitest", "~> 5.0"
26
26
  spec.add_development_dependency "rake-compiler"
27
27
 
28
- spec.add_dependency 'libv8', '~> 5.0', '< 5.1.11'
28
+ spec.add_dependency 'libv8', '~> 5.3'
29
29
  spec.require_paths = ["lib", "ext"]
30
30
 
31
31
  spec.extensions = ["ext/mini_racer_extension/extconf.rb"]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_racer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5.pre.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-06-15 00:00:00.000000000 Z
11
+ date: 2016-10-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -72,20 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '5.0'
76
- - - "<"
77
- - !ruby/object:Gem::Version
78
- version: 5.1.11
75
+ version: '5.3'
79
76
  type: :runtime
80
77
  prerelease: false
81
78
  version_requirements: !ruby/object:Gem::Requirement
82
79
  requirements:
83
80
  - - "~>"
84
81
  - !ruby/object:Gem::Version
85
- version: '5.0'
86
- - - "<"
87
- - !ruby/object:Gem::Version
88
- version: 5.1.11
82
+ version: '5.3'
89
83
  description: Minimal embedded v8 engine for Ruby
90
84
  email:
91
85
  - sam.saffron@gmail.com
@@ -125,9 +119,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
125
119
  version: '2.0'
126
120
  required_rubygems_version: !ruby/object:Gem::Requirement
127
121
  requirements:
128
- - - ">="
122
+ - - ">"
129
123
  - !ruby/object:Gem::Version
130
- version: '0'
124
+ version: 1.3.1
131
125
  requirements: []
132
126
  rubyforge_project:
133
127
  rubygems_version: 2.5.1