mini_racer 0.1.4 → 0.1.5.pre.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
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