sq_mini_racer 0.2.5.0.1.beta1 → 0.3.1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -26,46 +26,55 @@ def cppflags_add_cpu_extension!
26
26
  end
27
27
 
28
28
  def libv8_gem_name
29
- return "libv8-solaris" if IS_SOLARIS
30
- return "libv8-alpine" if IS_LINUX_MUSL
31
-
32
- 'libv8'
29
+ 'libv8-node'
33
30
  end
34
31
 
35
- def libv8_version
36
- '7.3.492.27.1'
32
+ def libv8_requirement
33
+ '~> 14.14.0.0.beta1'
37
34
  end
38
35
 
39
- def libv8_basename
40
- "#{libv8_gem_name}-#{libv8_version}-#{ruby_platform}"
36
+ def libv8_basename(version)
37
+ "#{libv8_gem_name}-#{version}-#{ruby_platform}"
41
38
  end
42
39
 
43
- def libv8_gemspec
44
- "#{libv8_basename}.gemspec"
40
+ def libv8_gemspec(version)
41
+ "#{libv8_basename(version)}.gemspec"
45
42
  end
46
43
 
47
- def libv8_local_path
48
- puts "looking for #{libv8_gemspec} in installed gems"
49
- candidates = Gem.path.map { |p| File.join(p, 'specifications', libv8_gemspec) }
50
- found = candidates.select { |f| File.exist?(f) }.first
44
+ def libv8_local_path(path=Gem.path)
45
+ name_glob = "#{libv8_gem_name}-*-#{ruby_platform}"
46
+
47
+ puts "looking for #{name_glob} in #{path.inspect}"
48
+
49
+ paths = path.map { |p| Dir.glob(File.join(p, 'specifications', name_glob + '.gemspec')) }.flatten
50
+
51
+ if paths.empty?
52
+ puts "#{name_glob} not found in #{path.inspect}"
53
+ return
54
+ end
55
+
56
+ specs = paths.map { |p| [p, eval(File.read(p))] }
57
+ .select { |_, spec| Gem::Requirement.new(libv8_requirement).satisfied_by?(spec.version) }
58
+ found_path, found_spec = specs.sort_by { |_, spec| spec.version }.last
51
59
 
52
- unless found
53
- puts "#{libv8_gemspec} not found in installed gems"
60
+ unless found_path && found_spec
61
+ puts "not found in specs: no '#{libv8_requirement}' in #{paths.inspect}"
54
62
  return
55
63
  end
56
64
 
57
- puts "found in installed specs: #{found}"
65
+ puts "found in specs: #{found_path}"
58
66
 
59
- dir = File.expand_path(File.join(found, '..', '..', 'gems', libv8_basename))
67
+ gemdir = File.basename(found_path, '.gemspec')
68
+ dir = File.expand_path(File.join(found_path, '..', '..', 'gems', gemdir))
60
69
 
61
70
  unless Dir.exist?(dir)
62
- puts "not found in installed gems: #{dir}"
71
+ puts "not found in gems: #{dir}"
63
72
  return
64
73
  end
65
74
 
66
- puts "found in installed gems: #{dir}"
75
+ puts "found in gems: #{dir}"
67
76
 
68
- dir
77
+ [dir, found_spec]
69
78
  end
70
79
 
71
80
  def vendor_path
@@ -73,25 +82,14 @@ def vendor_path
73
82
  end
74
83
 
75
84
  def libv8_vendor_path
76
- puts "looking for #{libv8_basename} in #{vendor_path}"
77
- path = Dir.glob("#{vendor_path}/#{libv8_basename}").first
78
-
79
- unless path
80
- puts "#{libv8_basename} not found in #{vendor_path}"
81
- return
82
- end
83
-
84
- puts "looking for #{libv8_basename}/lib/libv8.rb in #{vendor_path}"
85
- unless Dir.glob(File.join(vendor_path, libv8_basename, 'lib', 'libv8.rb')).first
86
- puts "#{libv8_basename}/lib/libv8.rb not found in #{vendor_path}"
87
- return
88
- end
89
-
90
- path
85
+ libv8_local_path([vendor_path])
91
86
  end
92
87
 
93
88
  def parse_platform(str)
94
- Gem::Platform.new(str).tap { |p| p.instance_eval { @os = 'linux-musl' } if str =~ /musl/ }
89
+ Gem::Platform.new(str).tap do |p|
90
+ p.instance_eval { @version = 'musl' } if str =~ /-musl/ && p.version.nil?
91
+ p.instance_eval { @cpu = 'x86_64' } if str =~ /universal.*darwin/
92
+ end
95
93
  end
96
94
 
97
95
  def ruby_platform
@@ -113,21 +111,23 @@ def libv8_remote_search
113
111
  json = JSON.parse(body)
114
112
 
115
113
  versions = json.select do |v|
116
- Gem::Version.new(v['number']) == Gem::Version.new(libv8_version)
114
+ Gem::Requirement.new(libv8_requirement).satisfied_by?(Gem::Version.new(v['number']))
117
115
  end
118
116
  abort(<<-ERROR) if versions.empty?
119
- ERROR: could not find #{libv8_version}
117
+ ERROR: could not find #{libv8_gem_name} (requirement #{libv8_requirement}) in rubygems.org
120
118
  ERROR
121
119
 
122
120
  platform_versions = versions.select do |v|
123
- parse_platform(v['platform']) == ruby_platform
121
+ parse_platform(v['platform']) == ruby_platform unless v['platform'] =~ /universal.*darwin/
124
122
  end
125
123
  abort(<<-ERROR) if platform_versions.empty?
126
- ERROR: found #{libv8_gem_name}-#{libv8_version}, but no binary for #{ruby_platform}
127
- try "gem install #{libv8_gem_name} -v '#{libv8_version}'" to attempt to build libv8 from source
124
+ ERROR: found gems matching #{libv8_gem_name}:'#{libv8_requirement}', but no binary for #{ruby_platform}
125
+ try "gem install #{libv8_gem_name}:'#{libv8_requirement}'" to attempt to build #{libv8_gem_name} from source
128
126
  ERROR
129
127
 
130
- platform_versions.first
128
+ puts "found #{libv8_gem_name} for #{ruby_platform} on rubygems: #{platform_versions.map { |v| v['number'] }.join(', ')}"
129
+
130
+ platform_versions.sort_by { |v| Gem::Version.new(v['number']) }.last
131
131
  end
132
132
 
133
133
  def libv8_download_uri(name, version, platform)
@@ -139,27 +139,47 @@ def libv8_downloaded_gem(name, version, platform)
139
139
  end
140
140
 
141
141
  def libv8_download(name, version, platform)
142
- FileUtils.mkdir_p(vendor_path)
142
+ FileUtils.mkdir_p(File.join(vendor_path, 'cache'))
143
143
  body = http_get(libv8_download_uri(name, version, platform))
144
- File.open(File.join(vendor_path, libv8_downloaded_gem(name, version, platform)), 'wb') { |f| f.write(body) }
144
+ File.open(File.join(vendor_path, 'cache', libv8_downloaded_gem(name, version, platform)), 'wb') { |f| f.write(body) }
145
+ end
146
+
147
+ def libv8_install!
148
+ cmd = "gem install #{libv8_gem_name} --version '#{libv8_requirement}' --install-dir '#{vendor_path}'"
149
+ puts "installing #{libv8_gem_name} using `#{cmd}`"
150
+ rc = system(cmd)
151
+
152
+ abort(<<-ERROR) unless rc
153
+ ERROR: could not install #{libv8_gem_name}:#{libv8_requirement}
154
+ try "gem install #{libv8_gem_name} -v '#{libv8_requirement}'" to attempt to build libv8 from source
155
+ ERROR
156
+
157
+ libv8_local_path([vendor_path])
145
158
  end
146
159
 
147
160
  def libv8_vendor!
161
+ return libv8_install! if Gem::VERSION < '2.0'
162
+
148
163
  version = libv8_remote_search
149
164
 
150
165
  puts "downloading #{libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform'])} to #{vendor_path}"
151
166
  libv8_download(libv8_gem_name, version['number'], version['platform'])
152
167
 
153
- package = Gem::Package.new(File.join(vendor_path, libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform'])))
154
- package.extract_files(File.join(vendor_path, File.basename(libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform']), '.gem')))
168
+ package = Gem::Package.new(File.join(vendor_path, 'cache', libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform'])))
169
+ package.extract_files(File.join(vendor_path, 'gems', File.basename(libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform']), '.gem')))
170
+ FileUtils.mkdir_p(File.join(vendor_path, 'specifications'))
171
+ File.open(File.join(vendor_path, 'specifications', File.basename(libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform']), '.gem') + '.gemspec'), 'wb') { |f| f.write(package.spec.to_ruby) }
155
172
 
156
173
  libv8_vendor_path
157
174
  end
158
175
 
159
176
  def ensure_libv8_load_path
160
- puts "detected platform #{RUBY_PLATFORM} => #{ruby_platform}"
177
+ puts "platform ruby:#{RUBY_PLATFORM} rubygems:#{Gem::Platform.new(RUBY_PLATFORM)} detected:#{ruby_platform}"
161
178
 
162
- libv8_path = libv8_local_path || libv8_vendor_path || libv8_vendor!
179
+ libv8_path, spec = libv8_local_path
180
+ if !ENV['ONLY_INSTALLED_LIBV8_GEM'] && !libv8_path
181
+ libv8_path, spec = libv8_vendor_path || libv8_vendor!
182
+ end
163
183
 
164
184
  abort(<<-ERROR) unless libv8_path
165
185
  ERROR: could not find #{libv8_gem_name}
@@ -171,7 +191,7 @@ end
171
191
 
172
192
  ensure_libv8_load_path
173
193
 
174
- require 'libv8'
194
+ require 'libv8-node'
175
195
 
176
196
  IS_DARWIN = RUBY_PLATFORM =~ /darwin/
177
197
 
@@ -184,17 +204,24 @@ $CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic"
184
204
  $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or IS_DARWIN
185
205
  $CPPFLAGS += " -std=c++0x"
186
206
  $CPPFLAGS += " -fpermissive"
207
+ $CPPFLAGS += " -DV8_COMPRESS_POINTERS"
208
+ $CPPFLAGS += " -fvisibility=hidden "
187
209
  cppflags_add_frame_pointer!
188
210
  cppflags_add_cpu_extension!
189
211
 
190
212
  $CPPFLAGS += " -Wno-reserved-user-defined-literal" if IS_DARWIN
191
213
 
192
214
  $LDFLAGS.insert(0, " -stdlib=libc++ ") if IS_DARWIN
215
+ $LDFLAGS += " -Wl,--no-undefined " unless IS_DARWIN
216
+ $LDFLAGS += " -Wl,-undefined,error " if IS_DARWIN
193
217
 
194
218
  if ENV['CXX']
195
219
  puts "SETTING CXX"
196
220
  CONFIG['CXX'] = ENV['CXX']
197
221
  end
222
+ # 1.9 has no $CXXFLAGS
223
+ $CPPFLAGS += " #{ENV['CPPFLAGS']}" if ENV['CPPFLAGS']
224
+ $LDFLAGS += " #{ENV['LDFLAGS']}" if ENV['LDFLAGS']
198
225
 
199
226
  CXX11_TEST = <<EOS
200
227
  #if __cplusplus <= 199711L
@@ -228,7 +255,7 @@ if enable_config('debug') || enable_config('asan')
228
255
  CONFIG['debugflags'] << ' -ggdb3 -O0'
229
256
  end
230
257
 
231
- Libv8.configure_makefile
258
+ Libv8::Node.configure_makefile
232
259
 
233
260
  if enable_config('asan')
234
261
  $CPPFLAGS.insert(0, " -fsanitize=address ")
@@ -36,6 +36,8 @@
36
36
  #include "compat.hpp"
37
37
  #include "simdutf8check.h"
38
38
 
39
+ #include <time.h>
40
+
39
41
  using namespace v8;
40
42
 
41
43
  typedef struct {
@@ -49,6 +51,7 @@ public:
49
51
  ArrayBuffer::Allocator* allocator;
50
52
  StartupData* startup_data;
51
53
  bool interrupted;
54
+ bool added_gc_cb;
52
55
  pid_t pid;
53
56
  VALUE mutex;
54
57
 
@@ -66,15 +69,12 @@ public:
66
69
 
67
70
 
68
71
  IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
69
- interrupted(false), pid(getpid()), refs_count(0) {
72
+ interrupted(false), added_gc_cb(false), pid(getpid()), refs_count(0) {
70
73
  VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
71
74
  mutex = rb_class_new_instance(0, nullptr, cMutex);
72
75
  }
73
76
 
74
- ~IsolateInfo() {
75
- void free_isolate(IsolateInfo*);
76
- free_isolate(this);
77
- }
77
+ ~IsolateInfo();
78
78
 
79
79
  void init(SnapshotInfo* snapshot_info = nullptr);
80
80
 
@@ -151,6 +151,7 @@ typedef struct {
151
151
  Local<Function> fun;
152
152
  Local<Value> *argv;
153
153
  EvalResult result;
154
+ size_t max_memory;
154
155
  } FunctionCall;
155
156
 
156
157
  enum IsolateFlags {
@@ -179,6 +180,11 @@ static VALUE rb_cDateTime = Qnil;
179
180
  static std::unique_ptr<Platform> current_platform = NULL;
180
181
  static std::mutex platform_lock;
181
182
 
183
+ static pthread_attr_t *thread_attr_p;
184
+ static pthread_rwlock_t exit_lock = PTHREAD_RWLOCK_INITIALIZER;
185
+ static bool ruby_exiting = false; // guarded by exit_lock
186
+ static bool single_threaded = false;
187
+
182
188
  static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
183
189
  bool platform_already_initialized = false;
184
190
 
@@ -190,6 +196,9 @@ static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
190
196
  platform_lock.lock();
191
197
 
192
198
  if (current_platform == NULL) {
199
+ if (!strcmp(RSTRING_PTR(flag_as_str), "--single_threaded")) {
200
+ single_threaded = true;
201
+ }
193
202
  V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
194
203
  } else {
195
204
  platform_already_initialized = true;
@@ -256,11 +265,13 @@ static void prepare_result(MaybeLocal<Value> v8res,
256
265
  Local<Value> local_value = v8res.ToLocalChecked();
257
266
  if ((local_value->IsObject() || local_value->IsArray()) &&
258
267
  !local_value->IsDate() && !local_value->IsFunction()) {
259
- Local<Object> JSON = context->Global()->Get(String::NewFromUtf8(isolate, "JSON"))
260
- ->ToObject(context).ToLocalChecked();
268
+ Local<Object> JSON = context->Global()->Get(
269
+ context, String::NewFromUtf8Literal(isolate, "JSON"))
270
+ .ToLocalChecked().As<Object>();
261
271
 
262
- Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
263
- .As<Function>();
272
+ Local<Function> stringify = JSON->Get(
273
+ context, v8::String::NewFromUtf8Literal(isolate, "stringify"))
274
+ .ToLocalChecked().As<Function>();
264
275
 
265
276
  Local<Object> object = local_value->ToObject(context).ToLocalChecked();
266
277
  const unsigned argc = 1;
@@ -314,7 +325,7 @@ static void prepare_result(MaybeLocal<Value> v8res,
314
325
  } else if(trycatch.HasTerminated()) {
315
326
  evalRes.terminated = true;
316
327
  evalRes.message = new Persistent<Value>();
317
- Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
328
+ Local<String> tmp = String::NewFromUtf8Literal(isolate, "JavaScript was terminated (either by timeout or explicitly)");
318
329
  evalRes.message->Reset(isolate, tmp);
319
330
  }
320
331
  if (!trycatch.StackTrace(context).IsEmpty()) {
@@ -335,7 +346,8 @@ nogvl_context_eval(void* arg) {
335
346
 
336
347
  EvalParams* eval_params = (EvalParams*)arg;
337
348
  EvalResult* result = eval_params->result;
338
- Isolate* isolate = eval_params->context_info->isolate_info->isolate;
349
+ IsolateInfo* isolate_info = eval_params->context_info->isolate_info;
350
+ Isolate* isolate = isolate_info->isolate;
339
351
 
340
352
  Isolate::Scope isolate_scope(isolate);
341
353
  HandleScope handle_scope(isolate);
@@ -379,7 +391,10 @@ nogvl_context_eval(void* arg) {
379
391
  // parsing successful
380
392
  if (eval_params->max_memory > 0) {
381
393
  isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
394
+ if (!isolate_info->added_gc_cb) {
382
395
  isolate->AddGCEpilogueCallback(gc_callback);
396
+ isolate_info->added_gc_cb = true;
397
+ }
383
398
  }
384
399
 
385
400
  maybe_value = parsed_script.ToLocalChecked()->Run(context);
@@ -392,6 +407,12 @@ nogvl_context_eval(void* arg) {
392
407
  return 0;
393
408
  }
394
409
 
410
+ static VALUE new_empty_failed_conv_obj() {
411
+ // TODO isolate code that translates execption to ruby
412
+ // exception so we can properly return it
413
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
414
+ }
415
+
395
416
  // assumes isolate locking is in place
396
417
  static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
397
418
  Local<Value> value) {
@@ -423,8 +444,11 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
423
444
  VALUE rb_array = rb_ary_new();
424
445
  Local<Array> arr = Local<Array>::Cast(value);
425
446
  for(uint32_t i=0; i < arr->Length(); i++) {
426
- Local<Value> element = arr->Get(i);
427
- VALUE rb_elem = convert_v8_to_ruby(isolate, context, element);
447
+ MaybeLocal<Value> element = arr->Get(context, i);
448
+ if (element.IsEmpty()) {
449
+ continue;
450
+ }
451
+ VALUE rb_elem = convert_v8_to_ruby(isolate, context, element.ToLocalChecked());
428
452
  if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
429
453
  return rb_elem;
430
454
  }
@@ -454,26 +478,47 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
454
478
  if (!maybe_props.IsEmpty()) {
455
479
  Local<Array> props = maybe_props.ToLocalChecked();
456
480
  for(uint32_t i=0; i < props->Length(); i++) {
457
- Local<Value> key = props->Get(i);
458
- VALUE rb_key = convert_v8_to_ruby(isolate, context, key);
459
- Local<Value> prop_value = object->Get(key);
460
- // this may have failed due to Get raising
481
+ MaybeLocal<Value> key = props->Get(context, i);
482
+ if (key.IsEmpty()) {
483
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
484
+ }
485
+ VALUE rb_key = convert_v8_to_ruby(isolate, context, key.ToLocalChecked());
461
486
 
462
- if (trycatch.HasCaught()) {
463
- // TODO isolate code that translates execption to ruby
464
- // exception so we can properly return it
465
- return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
487
+ MaybeLocal<Value> prop_value = object->Get(context, key.ToLocalChecked());
488
+ // this may have failed due to Get raising
489
+ if (prop_value.IsEmpty() || trycatch.HasCaught()) {
490
+ return new_empty_failed_conv_obj();
466
491
  }
467
492
 
468
- VALUE rb_value = convert_v8_to_ruby(isolate, context, prop_value);
493
+ VALUE rb_value = convert_v8_to_ruby(
494
+ isolate, context, prop_value.ToLocalChecked());
469
495
  rb_hash_aset(rb_hash, rb_key, rb_value);
470
496
  }
471
497
  }
472
498
  return rb_hash;
473
499
  }
474
500
 
475
- Local<String> rstr = value->ToString(context).ToLocalChecked();
476
- return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
501
+ if (value->IsSymbol()) {
502
+ v8::String::Utf8Value symbol_name(isolate,
503
+ Local<Symbol>::Cast(value)->Name());
504
+
505
+ VALUE str_symbol = rb_enc_str_new(
506
+ *symbol_name,
507
+ symbol_name.length(),
508
+ rb_enc_find("utf-8")
509
+ );
510
+
511
+ return ID2SYM(rb_intern_str(str_symbol));
512
+ }
513
+
514
+ MaybeLocal<String> rstr_maybe = value->ToString(context);
515
+
516
+ if (rstr_maybe.IsEmpty()) {
517
+ return Qnil;
518
+ } else {
519
+ Local<String> rstr = rstr_maybe.ToLocalChecked();
520
+ return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
521
+ }
477
522
  }
478
523
 
479
524
  static VALUE convert_v8_to_ruby(Isolate* isolate,
@@ -608,7 +653,7 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context,
608
653
  length = RARRAY_LEN(value);
609
654
  array = Array::New(isolate, (int)length);
610
655
  for(i=0; i<length; i++) {
611
- array->Set(i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
656
+ array->Set(context, i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
612
657
  }
613
658
  return scope.Escape(array);
614
659
  }
@@ -619,7 +664,7 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context,
619
664
  length = RARRAY_LEN(hash_as_array);
620
665
  for(i=0; i<length; i++) {
621
666
  pair = rb_ary_entry(hash_as_array, i);
622
- object->Set(convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
667
+ object->Set(context, convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
623
668
  convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 1)));
624
669
  }
625
670
  return scope.Escape(object);
@@ -661,8 +706,9 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context,
661
706
  value = rb_funcall(value, rb_intern("to_s"), 0);
662
707
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
663
708
  }
664
- return scope.Escape(String::NewFromUtf8(isolate, "Undefined Conversion"));
665
- }
709
+ return scope.Escape(
710
+ String::NewFromUtf8Literal(isolate, "Undefined Conversion"));
711
+ }
666
712
  }
667
713
  }
668
714
 
@@ -675,53 +721,43 @@ static void unblock_eval(void *ptr) {
675
721
  * The implementations of the run_extra_code(), create_snapshot_data_blob() and
676
722
  * warm_up_snapshot_data_blob() functions have been derived from V8's test suite.
677
723
  */
678
- bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
724
+ static bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
679
725
  const char *utf8_source, const char *name) {
680
726
  Context::Scope context_scope(context);
681
727
  TryCatch try_catch(isolate);
682
728
  Local<String> source_string;
683
- if (!String::NewFromUtf8(isolate, utf8_source,
684
- NewStringType::kNormal)
685
- .ToLocal(&source_string)) {
729
+ if (!String::NewFromUtf8(isolate, utf8_source).ToLocal(&source_string)) {
686
730
  return false;
687
731
  }
688
- Local<v8::String> resource_name =
689
- String::NewFromUtf8(isolate, name, NewStringType::kNormal)
690
- .ToLocalChecked();
732
+ Local<String> resource_name =
733
+ String::NewFromUtf8(isolate, name).ToLocalChecked();
691
734
  ScriptOrigin origin(resource_name);
692
735
  ScriptCompiler::Source source(source_string, origin);
693
736
  Local<Script> script;
694
737
  if (!ScriptCompiler::Compile(context, &source).ToLocal(&script))
695
738
  return false;
696
- if (script->Run(context).IsEmpty())
697
- return false;
698
- // CHECK(!try_catch.HasCaught());
739
+ if (script->Run(context).IsEmpty()) return false;
699
740
  return true;
700
741
  }
701
742
 
702
- StartupData
743
+ static StartupData
703
744
  create_snapshot_data_blob(const char *embedded_source = nullptr) {
704
- // Create a new isolate and a new context from scratch, optionally run
705
- // a script to embed, and serialize to create a snapshot blob.
706
- StartupData result = {nullptr, 0};
745
+ Isolate *isolate = Isolate::Allocate();
746
+
747
+ // Optionally run a script to embed, and serialize to create a snapshot blob.
748
+ SnapshotCreator snapshot_creator(isolate);
707
749
  {
708
- SnapshotCreator snapshot_creator;
709
- Isolate *isolate = snapshot_creator.GetIsolate();
710
- {
711
750
  HandleScope scope(isolate);
712
- Local<Context> context = Context::New(isolate);
751
+ Local<v8::Context> context = v8::Context::New(isolate);
713
752
  if (embedded_source != nullptr &&
714
- !run_extra_code(isolate, context, embedded_source,
715
- "<embedded>")) {
716
- return result;
753
+ !run_extra_code(isolate, context, embedded_source, "<embedded>")) {
754
+ return {};
717
755
  }
718
756
  snapshot_creator.SetDefaultContext(context);
719
757
  }
720
- result = snapshot_creator.CreateBlob(
758
+ return snapshot_creator.CreateBlob(
721
759
  SnapshotCreator::FunctionCodeHandling::kClear);
722
760
  }
723
- return result;
724
- }
725
761
 
726
762
  StartupData warm_up_snapshot_data_blob(StartupData cold_snapshot_blob,
727
763
  const char *warmup_source) {
@@ -868,6 +904,29 @@ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
868
904
  return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
869
905
  }
870
906
 
907
+ static VALUE rb_isolate_low_memory_notification(VALUE self) {
908
+ IsolateInfo* isolate_info;
909
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
910
+
911
+ if (current_platform == NULL) return Qfalse;
912
+
913
+ isolate_info->isolate->LowMemoryNotification();
914
+ return Qnil;
915
+ }
916
+
917
+ static VALUE rb_isolate_pump_message_loop(VALUE self) {
918
+ IsolateInfo* isolate_info;
919
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
920
+
921
+ if (current_platform == NULL) return Qfalse;
922
+
923
+ if (platform::PumpMessageLoop(current_platform.get(), isolate_info->isolate)){
924
+ return Qtrue;
925
+ } else {
926
+ return Qfalse;
927
+ }
928
+ }
929
+
871
930
  static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
872
931
  ContextInfo* context_info;
873
932
  Data_Get_Struct(self, ContextInfo, context_info);
@@ -1142,7 +1201,9 @@ gvl_ruby_callback(void* data) {
1142
1201
  callback_data.failed = false;
1143
1202
 
1144
1203
  if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
1145
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Terminated execution during transition from Ruby to JS"));
1204
+ args->GetIsolate()->ThrowException(
1205
+ String::NewFromUtf8Literal(args->GetIsolate(),
1206
+ "Terminated execution during transition from Ruby to JS"));
1146
1207
  args->GetIsolate()->TerminateExecution();
1147
1208
  if (length > 0) {
1148
1209
  rb_ary_clear(ruby_args);
@@ -1156,7 +1217,7 @@ gvl_ruby_callback(void* data) {
1156
1217
 
1157
1218
  if(callback_data.failed) {
1158
1219
  rb_iv_set(parent, "@current_exception", result);
1159
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
1220
+ args->GetIsolate()->ThrowException(String::NewFromUtf8Literal(args->GetIsolate(), "Ruby exception"));
1160
1221
  }
1161
1222
  else {
1162
1223
  HandleScope scope(args->GetIsolate());
@@ -1231,7 +1292,9 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1231
1292
 
1232
1293
  if (parent_object == Qnil) {
1233
1294
  context->Global()->Set(
1234
- v8_str, FunctionTemplate::New(isolate, ruby_callback, external)
1295
+ context,
1296
+ v8_str,
1297
+ FunctionTemplate::New(isolate, ruby_callback, external)
1235
1298
  ->GetFunction(context)
1236
1299
  .ToLocalChecked());
1237
1300
 
@@ -1244,7 +1307,7 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1244
1307
 
1245
1308
  MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
1246
1309
  if (parsed_script.IsEmpty()) {
1247
- parse_error = true;
1310
+ parse_error = true;
1248
1311
  } else {
1249
1312
  MaybeLocal<Value> maybe_value =
1250
1313
  parsed_script.ToLocalChecked()->Run(context);
@@ -1254,11 +1317,12 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1254
1317
  Local<Value> value = maybe_value.ToLocalChecked();
1255
1318
  if (value->IsObject()) {
1256
1319
  value.As<Object>()->Set(
1257
- v8_str, FunctionTemplate::New(
1258
- isolate, ruby_callback, external)
1320
+ context,
1321
+ v8_str,
1322
+ FunctionTemplate::New(isolate, ruby_callback, external)
1259
1323
  ->GetFunction(context)
1260
1324
  .ToLocalChecked());
1261
- attach_error = false;
1325
+ attach_error = false;
1262
1326
  }
1263
1327
  }
1264
1328
  }
@@ -1288,35 +1352,39 @@ static VALUE rb_context_isolate_mutex(VALUE self) {
1288
1352
  return context_info->isolate_info->mutex;
1289
1353
  }
1290
1354
 
1291
- void free_isolate(IsolateInfo* isolate_info) {
1292
-
1293
- if (isolate_info->isolate) {
1294
- Locker lock(isolate_info->isolate);
1295
- }
1296
-
1297
- if (isolate_info->isolate) {
1298
- if (isolate_info->interrupted) {
1299
- 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.\n");
1355
+ IsolateInfo::~IsolateInfo() {
1356
+ if (isolate) {
1357
+ if (this->interrupted) {
1358
+ fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, "
1359
+ "it can not be disposed and memory will not be "
1360
+ "reclaimed till the Ruby process exits.\n");
1300
1361
  } else {
1301
-
1302
- if (isolate_info->pid != getpid()) {
1303
- fprintf(stderr, "WARNING: V8 isolate was forked, it can not be disposed and memory will not be reclaimed till the Ruby process exits.\n");
1362
+ if (this->pid != getpid() && !single_threaded) {
1363
+ fprintf(stderr, "WARNING: V8 isolate was forked, "
1364
+ "it can not be disposed and "
1365
+ "memory will not be reclaimed "
1366
+ "till the Ruby process exits.\n"
1367
+ "It is VERY likely your process will hang.\n"
1368
+ "If you wish to use v8 in forked environment "
1369
+ "please ensure the platform is initialized with:\n"
1370
+ "MiniRacer::Platform.set_flags! :single_threaded\n"
1371
+ );
1304
1372
  } else {
1305
- isolate_info->isolate->Dispose();
1373
+ isolate->Dispose();
1306
1374
  }
1307
1375
  }
1308
- isolate_info->isolate = NULL;
1376
+ isolate = nullptr;
1309
1377
  }
1310
1378
 
1311
- if (isolate_info->startup_data) {
1312
- delete[] isolate_info->startup_data->data;
1313
- delete isolate_info->startup_data;
1379
+ if (startup_data) {
1380
+ delete[] startup_data->data;
1381
+ delete startup_data;
1314
1382
  }
1315
1383
 
1316
- delete isolate_info->allocator;
1384
+ delete allocator;
1317
1385
  }
1318
1386
 
1319
- static void *free_context_raw(void* arg) {
1387
+ static void free_context_raw(void *arg) {
1320
1388
  ContextInfo* context_info = (ContextInfo*)arg;
1321
1389
  IsolateInfo* isolate_info = context_info->isolate_info;
1322
1390
  Persistent<Context>* context = context_info->context;
@@ -1333,6 +1401,20 @@ static void *free_context_raw(void* arg) {
1333
1401
  }
1334
1402
 
1335
1403
  xfree(context_info);
1404
+ }
1405
+
1406
+ static void *free_context_thr(void* arg) {
1407
+ if (pthread_rwlock_tryrdlock(&exit_lock) != 0) {
1408
+ return NULL;
1409
+ }
1410
+ if (ruby_exiting) {
1411
+ return NULL;
1412
+ }
1413
+
1414
+ free_context_raw(arg);
1415
+
1416
+ pthread_rwlock_unlock(&exit_lock);
1417
+
1336
1418
  return NULL;
1337
1419
  }
1338
1420
 
@@ -1347,22 +1429,17 @@ static void free_context(ContextInfo* context_info) {
1347
1429
 
1348
1430
  if (isolate_info && isolate_info->refs() > 1) {
1349
1431
  pthread_t free_context_thread;
1350
- if (pthread_create(&free_context_thread, NULL, free_context_raw, (void*)context_info_copy)) {
1432
+ if (pthread_create(&free_context_thread, thread_attr_p,
1433
+ free_context_thr, (void*)context_info_copy)) {
1351
1434
  fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
1352
1435
  }
1353
-
1354
1436
  } else {
1355
1437
  free_context_raw(context_info_copy);
1356
1438
  }
1357
1439
 
1358
- if (context_info->context && isolate_info && isolate_info->isolate) {
1359
1440
  context_info->context = NULL;
1360
- }
1361
-
1362
- if (isolate_info) {
1363
1441
  context_info->isolate_info = NULL;
1364
1442
  }
1365
- }
1366
1443
 
1367
1444
  static void deallocate_isolate(void* data) {
1368
1445
 
@@ -1376,7 +1453,7 @@ static void mark_isolate(void* data) {
1376
1453
  isolate_info->mark();
1377
1454
  }
1378
1455
 
1379
- void deallocate(void* data) {
1456
+ static void deallocate(void* data) {
1380
1457
  ContextInfo* context_info = (ContextInfo*)data;
1381
1458
 
1382
1459
  free_context(context_info);
@@ -1391,22 +1468,22 @@ static void mark_context(void* data) {
1391
1468
  }
1392
1469
  }
1393
1470
 
1394
- void deallocate_external_function(void * data) {
1471
+ static void deallocate_external_function(void * data) {
1395
1472
  xfree(data);
1396
1473
  }
1397
1474
 
1398
- void deallocate_snapshot(void * data) {
1475
+ static void deallocate_snapshot(void * data) {
1399
1476
  SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
1400
1477
  delete[] snapshot_info->data;
1401
1478
  xfree(snapshot_info);
1402
1479
  }
1403
1480
 
1404
- VALUE allocate_external_function(VALUE klass) {
1481
+ static VALUE allocate_external_function(VALUE klass) {
1405
1482
  VALUE* self = ALLOC(VALUE);
1406
1483
  return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
1407
1484
  }
1408
1485
 
1409
- VALUE allocate(VALUE klass) {
1486
+ static VALUE allocate(VALUE klass) {
1410
1487
  ContextInfo* context_info = ALLOC(ContextInfo);
1411
1488
  context_info->isolate_info = NULL;
1412
1489
  context_info->context = NULL;
@@ -1414,7 +1491,7 @@ VALUE allocate(VALUE klass) {
1414
1491
  return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
1415
1492
  }
1416
1493
 
1417
- VALUE allocate_snapshot(VALUE klass) {
1494
+ static VALUE allocate_snapshot(VALUE klass) {
1418
1495
  SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
1419
1496
  snapshot_info->data = NULL;
1420
1497
  snapshot_info->raw_size = 0;
@@ -1422,7 +1499,7 @@ VALUE allocate_snapshot(VALUE klass) {
1422
1499
  return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
1423
1500
  }
1424
1501
 
1425
- VALUE allocate_isolate(VALUE klass) {
1502
+ static VALUE allocate_isolate(VALUE klass) {
1426
1503
  IsolateInfo* isolate_info = new IsolateInfo();
1427
1504
 
1428
1505
  return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
@@ -1519,6 +1596,8 @@ rb_heap_snapshot(VALUE self, VALUE file) {
1519
1596
  FileOutputStream stream(fp);
1520
1597
  snap->Serialize(&stream, HeapSnapshot::kJSON);
1521
1598
 
1599
+ fflush(fp);
1600
+
1522
1601
  const_cast<HeapSnapshot*>(snap)->Delete();
1523
1602
 
1524
1603
  return Qtrue;
@@ -1576,13 +1655,23 @@ nogvl_context_call(void *args) {
1576
1655
  if (!call) {
1577
1656
  return 0;
1578
1657
  }
1579
- Isolate* isolate = call->context_info->isolate_info->isolate;
1658
+ IsolateInfo *isolate_info = call->context_info->isolate_info;
1659
+ Isolate* isolate = isolate_info->isolate;
1580
1660
 
1581
1661
  // in gvl flag
1582
1662
  isolate->SetData(IN_GVL, (void*)false);
1583
1663
  // terminate ASAP
1584
1664
  isolate->SetData(DO_TERMINATE, (void*)false);
1585
1665
 
1666
+ if (call->max_memory > 0) {
1667
+ isolate->SetData(MEM_SOFTLIMIT_VALUE, &call->max_memory);
1668
+ isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)false);
1669
+ if (!isolate_info->added_gc_cb) {
1670
+ isolate->AddGCEpilogueCallback(gc_callback);
1671
+ isolate_info->added_gc_cb = true;
1672
+ }
1673
+ }
1674
+
1586
1675
  Isolate::Scope isolate_scope(isolate);
1587
1676
  EscapableHandleScope handle_scope(isolate);
1588
1677
  TryCatch trycatch(isolate);
@@ -1640,6 +1729,13 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1640
1729
  call_argv = argv + 1;
1641
1730
  }
1642
1731
 
1732
+ call.max_memory = 0;
1733
+ VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
1734
+ if (mem_softlimit != Qnil) {
1735
+ unsigned long sl_int = NUM2ULONG(mem_softlimit);
1736
+ call.max_memory = (size_t)sl_int;
1737
+ }
1738
+
1643
1739
  bool missingFunction = false;
1644
1740
  {
1645
1741
  Locker lock(isolate);
@@ -1651,8 +1747,11 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1651
1747
 
1652
1748
  // examples of such usage can be found in
1653
1749
  // https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
1654
- Local<String> fname = String::NewFromUtf8(isolate, call.function_name);
1655
- MaybeLocal<v8::Value> val = context->Global()->Get(fname);
1750
+ MaybeLocal<String> fname = String::NewFromUtf8(isolate, call.function_name);
1751
+ MaybeLocal<v8::Value> val;
1752
+ if (!fname.IsEmpty()) {
1753
+ val = context->Global()->Get(context, fname.ToLocalChecked());
1754
+ }
1656
1755
 
1657
1756
  if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1658
1757
  missingFunction = true;
@@ -1701,12 +1800,44 @@ static VALUE rb_context_create_isolate_value(VALUE self) {
1701
1800
  return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
1702
1801
  }
1703
1802
 
1803
+ static void set_ruby_exiting(VALUE value) {
1804
+ (void)value;
1805
+
1806
+ int res = pthread_rwlock_wrlock(&exit_lock);
1807
+
1808
+ ruby_exiting = true;
1809
+ if (res == 0) {
1810
+ pthread_rwlock_unlock(&exit_lock);
1811
+ }
1812
+ }
1813
+
1814
+ static VALUE rb_monotime(VALUE self) {
1815
+ struct timespec ts;
1816
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
1817
+ return INT2FIX(-1);
1818
+ }
1819
+
1820
+ return DBL2NUM(
1821
+ (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0);
1822
+ }
1823
+
1704
1824
  extern "C" {
1705
1825
 
1706
- void Init_sq_mini_racer_extension ( void )
1826
+ __attribute__((visibility("default"))) void Init_sq_mini_racer_extension ( void )
1707
1827
  {
1708
- VALUE rb_mSqreen = rb_define_module("Sqreen");
1828
+ ID sqreen_id = rb_intern("Sqreen");
1829
+ VALUE rb_mSqreen;
1830
+ if (rb_const_defined(rb_cObject, sqreen_id)) {
1831
+ rb_mSqreen = rb_const_get(rb_cObject, sqreen_id);
1832
+ if (TYPE(rb_mSqreen) != T_MODULE) {
1833
+ rb_raise(rb_eTypeError, "Sqreen is not a module");
1834
+ return;
1835
+ }
1836
+ } else {
1837
+ rb_mSqreen = rb_define_module("Sqreen");
1838
+ }
1709
1839
  VALUE rb_mMiniRacer = rb_define_module_under(rb_mSqreen, "MiniRacer");
1840
+ rb_define_module_function(rb_mMiniRacer, "monotime", (VALUE(*)(...))&rb_monotime, 0);
1710
1841
  rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1711
1842
  rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
1712
1843
  rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
@@ -1753,8 +1884,23 @@ extern "C" {
1753
1884
  rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1754
1885
 
1755
1886
  rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
1887
+ rb_define_method(rb_cIsolate, "low_memory_notification", (VALUE(*)(...))&rb_isolate_low_memory_notification, 0);
1888
+ rb_define_method(rb_cIsolate, "pump_message_loop", (VALUE(*)(...))&rb_isolate_pump_message_loop, 0);
1756
1889
  rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
1757
1890
 
1758
1891
  rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
1892
+
1893
+ rb_set_end_proc(set_ruby_exiting, Qnil);
1894
+
1895
+ static pthread_attr_t attr;
1896
+ if (pthread_attr_init(&attr) == 0) {
1897
+ if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0) {
1898
+ thread_attr_p = &attr;
1899
+ }
1900
+ }
1901
+ auto on_fork_for_child = []() {
1902
+ exit_lock = PTHREAD_RWLOCK_INITIALIZER;
1903
+ };
1904
+ pthread_atfork(nullptr, nullptr, on_fork_for_child);
1759
1905
  }
1760
1906
  }