sq_mini_racer 0.2.4.sqreen3 → 0.3.1.0.1

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.
@@ -1,33 +1,226 @@
1
1
  require 'mkmf'
2
+
2
3
  require 'fileutils'
4
+ require 'net/http'
5
+ require 'json'
6
+ require 'rubygems'
7
+ require 'rubygems/package'
3
8
 
4
- IS_DARWIN = RUBY_PLATFORM =~ /darwin/
5
9
  IS_SOLARIS = RUBY_PLATFORM =~ /solaris/
10
+ IS_LINUX_MUSL = RUBY_PLATFORM =~ /linux-musl/
11
+
12
+ def cppflags_clear_std!
13
+ $CPPFLAGS.gsub! /-std=[^\s]+/, ''
14
+ end
15
+
16
+ def cppflags_add_frame_pointer!
17
+ $CPPFLAGS += " -fno-omit-frame-pointer"
18
+ end
19
+
20
+ def cppflags_add_cpu_extension!
21
+ if enable_config('avx2')
22
+ $CPPFLAGS += " -mavx2"
23
+ else
24
+ $CPPFLAGS += " -mssse3"
25
+ end
26
+ end
27
+
28
+ def libv8_gem_name
29
+ 'libv8-node'
30
+ end
31
+
32
+ def libv8_requirement
33
+ '~> 14.14.0.0.beta1'
34
+ end
35
+
36
+ def libv8_basename(version)
37
+ "#{libv8_gem_name}-#{version}-#{ruby_platform}"
38
+ end
39
+
40
+ def libv8_gemspec(version)
41
+ "#{libv8_basename(version)}.gemspec"
42
+ end
43
+
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
59
+
60
+ unless found_path && found_spec
61
+ puts "not found in specs: no '#{libv8_requirement}' in #{paths.inspect}"
62
+ return
63
+ end
64
+
65
+ puts "found in specs: #{found_path}"
66
+
67
+ gemdir = File.basename(found_path, '.gemspec')
68
+ dir = File.expand_path(File.join(found_path, '..', '..', 'gems', gemdir))
69
+
70
+ unless Dir.exist?(dir)
71
+ puts "not found in gems: #{dir}"
72
+ return
73
+ end
74
+
75
+ puts "found in gems: #{dir}"
76
+
77
+ [dir, found_spec]
78
+ end
79
+
80
+ def vendor_path
81
+ File.join(Dir.pwd, 'vendor')
82
+ end
83
+
84
+ def libv8_vendor_path
85
+ libv8_local_path([vendor_path])
86
+ end
87
+
88
+ def parse_platform(str)
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
93
+ end
94
+
95
+ def ruby_platform
96
+ parse_platform(RUBY_PLATFORM)
97
+ end
98
+
99
+ def http_get(uri)
100
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
101
+ res = http.get(uri.path)
102
+
103
+ abort("HTTP error #{res.code}: #{uri}") unless res.code == '200'
104
+
105
+ return res.body
106
+ end
107
+ end
108
+
109
+ def libv8_remote_search
110
+ body = http_get(URI("https://rubygems.org/api/v1/versions/#{libv8_gem_name}.json"))
111
+ json = JSON.parse(body)
112
+
113
+ versions = json.select do |v|
114
+ Gem::Requirement.new(libv8_requirement).satisfied_by?(Gem::Version.new(v['number']))
115
+ end
116
+ abort(<<-ERROR) if versions.empty?
117
+ ERROR: could not find #{libv8_gem_name} (requirement #{libv8_requirement}) in rubygems.org
118
+ ERROR
119
+
120
+ platform_versions = versions.select do |v|
121
+ parse_platform(v['platform']) == ruby_platform unless v['platform'] =~ /universal.*darwin/
122
+ end
123
+ abort(<<-ERROR) if platform_versions.empty?
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
126
+ ERROR
127
+
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
+ end
132
+
133
+ def libv8_download_uri(name, version, platform)
134
+ URI("https://rubygems.org/downloads/#{name}-#{version}-#{platform}.gem")
135
+ end
136
+
137
+ def libv8_downloaded_gem(name, version, platform)
138
+ "#{name}-#{version}-#{platform}.gem"
139
+ end
140
+
141
+ def libv8_download(name, version, platform)
142
+ FileUtils.mkdir_p(File.join(vendor_path, 'cache'))
143
+ body = http_get(libv8_download_uri(name, version, platform))
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])
158
+ end
159
+
160
+ def libv8_vendor!
161
+ return libv8_install! if Gem::VERSION < '2.0'
162
+
163
+ version = libv8_remote_search
164
+
165
+ puts "downloading #{libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform'])} to #{vendor_path}"
166
+ libv8_download(libv8_gem_name, version['number'], version['platform'])
167
+
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) }
172
+
173
+ libv8_vendor_path
174
+ end
175
+
176
+ def ensure_libv8_load_path
177
+ puts "platform ruby:#{RUBY_PLATFORM} rubygems:#{Gem::Platform.new(RUBY_PLATFORM)} detected:#{ruby_platform}"
178
+
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
183
+
184
+ abort(<<-ERROR) unless libv8_path
185
+ ERROR: could not find #{libv8_gem_name}
186
+ ERROR
187
+
188
+ $LOAD_PATH.unshift(File.join(libv8_path, 'ext'))
189
+ $LOAD_PATH.unshift(File.join(libv8_path, 'lib'))
190
+ end
191
+
192
+ ensure_libv8_load_path
193
+
194
+ require 'libv8-node'
195
+
196
+ IS_DARWIN = RUBY_PLATFORM =~ /darwin/
6
197
 
7
198
  have_library('pthread')
8
199
  have_library('objc') if IS_DARWIN
9
- $CPPFLAGS.gsub! /-std=[^\s]+/, ''
200
+ cppflags_clear_std!
10
201
  $CPPFLAGS += " -Wall" unless $CPPFLAGS.split.include? "-Wall"
11
202
  $CPPFLAGS += " -g" unless $CPPFLAGS.split.include? "-g"
12
203
  $CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic"
13
204
  $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or IS_DARWIN
14
205
  $CPPFLAGS += " -std=c++0x"
15
206
  $CPPFLAGS += " -fpermissive"
16
- $CPPFLAGS += " -fno-omit-frame-pointer"
17
- if enable_config('avx2')
18
- $CPPFLAGS += " -mavx2"
19
- else
20
- $CPPFLAGS += " -mssse3"
21
- end
207
+ $CPPFLAGS += " -DV8_COMPRESS_POINTERS"
208
+ $CPPFLAGS += " -fvisibility=hidden "
209
+ cppflags_add_frame_pointer!
210
+ cppflags_add_cpu_extension!
22
211
 
23
212
  $CPPFLAGS += " -Wno-reserved-user-defined-literal" if IS_DARWIN
24
213
 
25
214
  $LDFLAGS.insert(0, " -stdlib=libc++ ") if IS_DARWIN
215
+ $LDFLAGS += " -Wl,--no-undefined " unless IS_DARWIN
26
216
 
27
217
  if ENV['CXX']
28
218
  puts "SETTING CXX"
29
219
  CONFIG['CXX'] = ENV['CXX']
30
220
  end
221
+ # 1.9 has no $CXXFLAGS
222
+ $CPPFLAGS += " #{ENV['CPPFLAGS']}" if ENV['CPPFLAGS']
223
+ $LDFLAGS += " #{ENV['LDFLAGS']}" if ENV['LDFLAGS']
31
224
 
32
225
  CXX11_TEST = <<EOS
33
226
  #if __cplusplus <= 199711L
@@ -57,103 +250,15 @@ if CONFIG['warnflags']
57
250
  CONFIG['warnflags'].gsub!('-Wimplicit-function-declaration', '')
58
251
  end
59
252
 
60
- if enable_config('debug')
253
+ if enable_config('debug') || enable_config('asan')
61
254
  CONFIG['debugflags'] << ' -ggdb3 -O0'
62
255
  end
63
256
 
64
- def fixup_libtinfo
65
- dirs = %w[/lib64 /usr/lib64 /lib /usr/lib]
66
- found_v5 = dirs.map { |d| "#{d}/libtinfo.so.5" }.find &File.method(:file?)
67
- return '' if found_v5
68
- found_v6 = dirs.map { |d| "#{d}/libtinfo.so.6" }.find &File.method(:file?)
69
- return '' unless found_v6
70
- FileUtils.ln_s found_v6, 'gemdir/libtinfo.so.5', :force => true
71
- "LD_LIBRARY_PATH='#{File.expand_path('gemdir')}:#{ENV['LD_LIBRARY_PATH']}'"
72
- end
73
-
74
- def libv8_gem_name
75
- return "libv8-solaris" if IS_SOLARIS
76
-
77
- is_musl = false
78
- begin
79
- is_musl = !!(File.read('/proc/self/maps') =~ /ld-musl-x86_64/)
80
- rescue; end
81
-
82
- is_musl ? 'libv8-alpine' : 'libv8'
83
- end
84
-
85
- # 1) old rubygem versions prefer source gems to binary ones
86
- # ... and their --platform switch is broken too, as it leaves the 'ruby'
87
- # platform in Gem.platforms.
88
- # 2) the ruby binaries distributed with alpine (platform ending in -musl)
89
- # refuse to load binary gems by default
90
- def force_platform_gem
91
- gem_version = `gem --version`
92
- return 'gem' unless $?.success?
93
-
94
- if RUBY_PLATFORM != 'x86_64-linux-musl'
95
- return 'gem' if gem_version.to_f.zero? || gem_version.to_f >= 2.3
96
- return 'gem' if RUBY_PLATFORM != 'x86_64-linux'
97
- end
98
-
99
- gem_binary = `which gem`
100
- return 'gem' unless $?.success?
101
-
102
- ruby = File.foreach(gem_binary.strip).first.sub(/^#!/, '').strip
103
- unless File.file? ruby
104
- warn "No valid ruby: #{ruby}"
105
- return 'gem'
106
- end
107
-
108
- require 'tempfile'
109
- file = Tempfile.new('sq_mini_racer')
110
- file << <<EOS
111
- require 'rubygems'
112
- platforms = Gem.platforms
113
- platforms.reject! { |it| it == 'ruby' }
114
- if platforms.empty?
115
- platforms << Gem::Platform.new('x86_64-linux')
116
- end
117
- Gem.send(:define_method, :platforms) { platforms }
118
- #{IO.read(gem_binary.strip)}
119
- EOS
120
- file.close
121
- "#{ruby} '#{file.path}'"
122
- end
123
-
124
- LIBV8_VERSION = '6.7.288.46.1'
125
- libv8_rb = Dir.glob('**/libv8.rb').first
126
- FileUtils.mkdir_p('gemdir')
127
- unless libv8_rb
128
- gem_name = libv8_gem_name
129
- cmd = "#{fixup_libtinfo} #{force_platform_gem} install --version '= #{LIBV8_VERSION}' --install-dir gemdir #{gem_name}"
130
- puts "Will try downloading #{gem_name} gem: #{cmd}"
131
- `#{cmd}`
132
- unless $?.success?
133
- warn <<EOS
134
-
135
- WARNING: Could not download a private copy of the libv8 gem. Please make
136
- sure that you have internet access and that the `gem` binary is available.
137
-
138
- EOS
139
- end
140
-
141
- libv8_rb = Dir.glob('**/libv8.rb').first
142
- unless libv8_rb
143
- warn <<EOS
144
-
145
- WARNING: Could not find libv8 after the local copy of libv8 having supposedly
146
- been installed.
147
-
148
- EOS
149
- end
150
- end
257
+ Libv8::Node.configure_makefile
151
258
 
152
- if libv8_rb
153
- $:.unshift(File.dirname(libv8_rb) + '/../ext')
154
- $:.unshift File.dirname(libv8_rb)
259
+ if enable_config('asan')
260
+ $CPPFLAGS.insert(0, " -fsanitize=address ")
261
+ $LDFLAGS.insert(0, " -fsanitize=address ")
155
262
  end
156
263
 
157
- require 'libv8'
158
- Libv8.configure_makefile
159
264
  create_makefile 'sq_mini_racer_extension'
@@ -23,7 +23,9 @@
23
23
  #if RUBY_API_VERSION_MAJOR > 1
24
24
  #include <ruby/thread.h>
25
25
  #endif
26
+ #include <ruby/io.h>
26
27
  #include <v8.h>
28
+ #include <v8-profiler.h>
27
29
  #include <libplatform/libplatform.h>
28
30
  #include <ruby/encoding.h>
29
31
  #include <pthread.h>
@@ -34,6 +36,8 @@
34
36
  #include "compat.hpp"
35
37
  #include "simdutf8check.h"
36
38
 
39
+ #include <time.h>
40
+
37
41
  using namespace v8;
38
42
 
39
43
  typedef struct {
@@ -47,6 +51,7 @@ public:
47
51
  ArrayBuffer::Allocator* allocator;
48
52
  StartupData* startup_data;
49
53
  bool interrupted;
54
+ bool added_gc_cb;
50
55
  pid_t pid;
51
56
  VALUE mutex;
52
57
 
@@ -64,15 +69,12 @@ public:
64
69
 
65
70
 
66
71
  IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
67
- interrupted(false), pid(getpid()), refs_count(0) {
72
+ interrupted(false), added_gc_cb(false), pid(getpid()), refs_count(0) {
68
73
  VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
69
74
  mutex = rb_class_new_instance(0, nullptr, cMutex);
70
75
  }
71
76
 
72
- ~IsolateInfo() {
73
- void free_isolate(IsolateInfo*);
74
- free_isolate(this);
75
- }
77
+ ~IsolateInfo();
76
78
 
77
79
  void init(SnapshotInfo* snapshot_info = nullptr);
78
80
 
@@ -149,6 +151,7 @@ typedef struct {
149
151
  Local<Function> fun;
150
152
  Local<Value> *argv;
151
153
  EvalResult result;
154
+ size_t max_memory;
152
155
  } FunctionCall;
153
156
 
154
157
  enum IsolateFlags {
@@ -174,9 +177,14 @@ static VALUE rb_mJSON;
174
177
  static VALUE rb_cFailedV8Conversion;
175
178
  static VALUE rb_cDateTime = Qnil;
176
179
 
177
- static Platform* current_platform = NULL;
180
+ static std::unique_ptr<Platform> current_platform = NULL;
178
181
  static std::mutex platform_lock;
179
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
+
180
188
  static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
181
189
  bool platform_already_initialized = false;
182
190
 
@@ -188,6 +196,9 @@ static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
188
196
  platform_lock.lock();
189
197
 
190
198
  if (current_platform == NULL) {
199
+ if (!strcmp(RSTRING_PTR(flag_as_str), "--single_threaded")) {
200
+ single_threaded = true;
201
+ }
191
202
  V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
192
203
  } else {
193
204
  platform_already_initialized = true;
@@ -211,8 +222,8 @@ static void init_v8() {
211
222
 
212
223
  if (current_platform == NULL) {
213
224
  V8::InitializeICU();
214
- current_platform = platform::CreateDefaultPlatform();
215
- V8::InitializePlatform(current_platform);
225
+ current_platform = platform::NewDefaultPlatform();
226
+ V8::InitializePlatform(current_platform.get());
216
227
  V8::Initialize();
217
228
  }
218
229
 
@@ -255,15 +266,17 @@ static void prepare_result(MaybeLocal<Value> v8res,
255
266
  if ((local_value->IsObject() || local_value->IsArray()) &&
256
267
  !local_value->IsDate() && !local_value->IsFunction()) {
257
268
  Local<Object> JSON = context->Global()->Get(
258
- String::NewFromUtf8(isolate, "JSON"))->ToObject();
269
+ context, String::NewFromUtf8Literal(isolate, "JSON"))
270
+ .ToLocalChecked().As<Object>();
259
271
 
260
- Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
261
- .As<Function>();
272
+ Local<Function> stringify = JSON->Get(
273
+ context, v8::String::NewFromUtf8Literal(isolate, "stringify"))
274
+ .ToLocalChecked().As<Function>();
262
275
 
263
- Local<Object> object = local_value->ToObject();
276
+ Local<Object> object = local_value->ToObject(context).ToLocalChecked();
264
277
  const unsigned argc = 1;
265
278
  Local<Value> argv[argc] = { object };
266
- MaybeLocal<Value> json = stringify->Call(JSON, argc, argv);
279
+ MaybeLocal<Value> json = stringify->Call(context, JSON, argc, argv);
267
280
 
268
281
  if (json.IsEmpty()) {
269
282
  evalRes.executed = false;
@@ -287,11 +300,21 @@ static void prepare_result(MaybeLocal<Value> v8res,
287
300
  evalRes.message = new Persistent<Value>();
288
301
  Local<Message> message = trycatch.Message();
289
302
  char buf[1000];
290
- int len;
303
+ int len, line, column;
304
+
305
+ if (!message->GetLineNumber(context).To(&line)) {
306
+ line = 0;
307
+ }
308
+
309
+ if (!message->GetStartColumn(context).To(&column)) {
310
+ column = 0;
311
+ }
312
+
291
313
  len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(isolate, message->Get()),
292
- *String::Utf8Value(isolate, message->GetScriptResourceName()->ToString()),
293
- message->GetLineNumber(),
294
- message->GetStartColumn());
314
+ *String::Utf8Value(isolate, message->GetScriptResourceName()->ToString(context).ToLocalChecked()),
315
+ line,
316
+ column);
317
+
295
318
  if ((size_t) len >= sizeof(buf)) {
296
319
  len = sizeof(buf) - 1;
297
320
  buf[len] = '\0';
@@ -302,12 +325,13 @@ static void prepare_result(MaybeLocal<Value> v8res,
302
325
  } else if(trycatch.HasTerminated()) {
303
326
  evalRes.terminated = true;
304
327
  evalRes.message = new Persistent<Value>();
305
- 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)");
306
329
  evalRes.message->Reset(isolate, tmp);
307
330
  }
308
331
  if (!trycatch.StackTrace(context).IsEmpty()) {
309
332
  evalRes.backtrace = new Persistent<Value>();
310
- evalRes.backtrace->Reset(isolate, trycatch.StackTrace(context).ToLocalChecked()->ToString());
333
+ evalRes.backtrace->Reset(isolate,
334
+ trycatch.StackTrace(context).ToLocalChecked()->ToString(context).ToLocalChecked());
311
335
  }
312
336
  }
313
337
  }
@@ -322,7 +346,8 @@ nogvl_context_eval(void* arg) {
322
346
 
323
347
  EvalParams* eval_params = (EvalParams*)arg;
324
348
  EvalResult* result = eval_params->result;
325
- 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;
326
351
 
327
352
  Isolate::Scope isolate_scope(isolate);
328
353
  HandleScope handle_scope(isolate);
@@ -366,7 +391,10 @@ nogvl_context_eval(void* arg) {
366
391
  // parsing successful
367
392
  if (eval_params->max_memory > 0) {
368
393
  isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
394
+ if (!isolate_info->added_gc_cb) {
369
395
  isolate->AddGCEpilogueCallback(gc_callback);
396
+ isolate_info->added_gc_cb = true;
397
+ }
370
398
  }
371
399
 
372
400
  maybe_value = parsed_script.ToLocalChecked()->Run(context);
@@ -379,6 +407,12 @@ nogvl_context_eval(void* arg) {
379
407
  return 0;
380
408
  }
381
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
+
382
416
  // assumes isolate locking is in place
383
417
  static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
384
418
  Local<Value> value) {
@@ -391,11 +425,11 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
391
425
  }
392
426
 
393
427
  if (value->IsInt32()) {
394
- return INT2FIX(value->Int32Value());
428
+ return INT2FIX(value->Int32Value(context).ToChecked());
395
429
  }
396
430
 
397
431
  if (value->IsNumber()) {
398
- return rb_float_new(value->NumberValue());
432
+ return rb_float_new(value->NumberValue(context).ToChecked());
399
433
  }
400
434
 
401
435
  if (value->IsTrue()) {
@@ -410,8 +444,11 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
410
444
  VALUE rb_array = rb_ary_new();
411
445
  Local<Array> arr = Local<Array>::Cast(value);
412
446
  for(uint32_t i=0; i < arr->Length(); i++) {
413
- Local<Value> element = arr->Get(i);
414
- 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());
415
452
  if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
416
453
  return rb_elem;
417
454
  }
@@ -436,31 +473,52 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
436
473
  VALUE rb_hash = rb_hash_new();
437
474
  TryCatch trycatch(isolate);
438
475
 
439
- Local<Object> object = value->ToObject();
476
+ Local<Object> object = value->ToObject(context).ToLocalChecked();
440
477
  auto maybe_props = object->GetOwnPropertyNames(context);
441
478
  if (!maybe_props.IsEmpty()) {
442
479
  Local<Array> props = maybe_props.ToLocalChecked();
443
480
  for(uint32_t i=0; i < props->Length(); i++) {
444
- Local<Value> key = props->Get(i);
445
- VALUE rb_key = convert_v8_to_ruby(isolate, context, key);
446
- Local<Value> prop_value = object->Get(key);
447
- // 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());
448
486
 
449
- if (trycatch.HasCaught()) {
450
- // TODO isolate code that translates execption to ruby
451
- // exception so we can properly return it
452
- 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();
453
491
  }
454
492
 
455
- 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());
456
495
  rb_hash_aset(rb_hash, rb_key, rb_value);
457
496
  }
458
497
  }
459
498
  return rb_hash;
460
499
  }
461
500
 
462
- Local<String> rstr = value->ToString();
463
- return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(), 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
+ }
464
522
  }
465
523
 
466
524
  static VALUE convert_v8_to_ruby(Isolate* isolate,
@@ -557,7 +615,7 @@ treat_as_latin1:
557
615
  return v8str.ToLocalChecked();
558
616
  }
559
617
 
560
- static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value)
618
+ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context, VALUE value)
561
619
  {
562
620
  EscapableHandleScope scope(isolate);
563
621
 
@@ -571,87 +629,87 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value)
571
629
  VALUE klass;
572
630
 
573
631
  switch (TYPE(value)) {
574
- case T_FIXNUM:
632
+ case T_FIXNUM:
575
633
  {
576
- fixnum = NUM2LONG(value);
577
- if (fixnum > INT_MAX)
578
- {
579
- return scope.Escape(Number::New(isolate, (double)fixnum));
580
- }
581
- return scope.Escape(Integer::New(isolate, (int)fixnum));
634
+ fixnum = NUM2LONG(value);
635
+ if (fixnum > INT_MAX)
636
+ {
637
+ return scope.Escape(Number::New(isolate, (double)fixnum));
582
638
  }
583
- case T_FLOAT:
584
- return scope.Escape(Number::New(isolate, NUM2DBL(value)));
585
- case T_STRING:
639
+ return scope.Escape(Integer::New(isolate, (int)fixnum));
640
+ }
641
+ case T_FLOAT:
642
+ return scope.Escape(Number::New(isolate, NUM2DBL(value)));
643
+ case T_STRING:
586
644
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
587
- case T_NIL:
588
- return scope.Escape(Null(isolate));
589
- case T_TRUE:
590
- return scope.Escape(True(isolate));
591
- case T_FALSE:
592
- return scope.Escape(False(isolate));
593
- case T_ARRAY:
645
+ case T_NIL:
646
+ return scope.Escape(Null(isolate));
647
+ case T_TRUE:
648
+ return scope.Escape(True(isolate));
649
+ case T_FALSE:
650
+ return scope.Escape(False(isolate));
651
+ case T_ARRAY:
594
652
  {
595
- length = RARRAY_LEN(value);
596
- array = Array::New(isolate, (int)length);
597
- for(i=0; i<length; i++) {
598
- array->Set(i, convert_ruby_to_v8(isolate, rb_ary_entry(value, i)));
599
- }
600
- return scope.Escape(array);
653
+ length = RARRAY_LEN(value);
654
+ array = Array::New(isolate, (int)length);
655
+ for(i=0; i<length; i++) {
656
+ array->Set(context, i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
657
+ }
658
+ return scope.Escape(array);
601
659
  }
602
- case T_HASH:
660
+ case T_HASH:
603
661
  {
604
- object = Object::New(isolate);
605
- hash_as_array = rb_funcall(value, rb_intern("to_a"), 0);
606
- length = RARRAY_LEN(hash_as_array);
607
- for(i=0; i<length; i++) {
608
- pair = rb_ary_entry(hash_as_array, i);
609
- object->Set(convert_ruby_to_v8(isolate, rb_ary_entry(pair, 0)),
610
- convert_ruby_to_v8(isolate, rb_ary_entry(pair, 1)));
611
- }
612
- return scope.Escape(object);
662
+ object = Object::New(isolate);
663
+ hash_as_array = rb_funcall(value, rb_intern("to_a"), 0);
664
+ length = RARRAY_LEN(hash_as_array);
665
+ for(i=0; i<length; i++) {
666
+ pair = rb_ary_entry(hash_as_array, i);
667
+ object->Set(context, convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
668
+ convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 1)));
669
+ }
670
+ return scope.Escape(object);
613
671
  }
614
- case T_SYMBOL:
672
+ case T_SYMBOL:
615
673
  {
616
- value = rb_funcall(value, rb_intern("to_s"), 0);
674
+ value = rb_funcall(value, rb_intern("to_s"), 0);
617
675
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
618
676
  }
619
- case T_DATA:
677
+ case T_DATA:
620
678
  {
621
- klass = rb_funcall(value, rb_intern("class"), 0);
622
- if (klass == rb_cTime || klass == rb_cDateTime)
679
+ klass = rb_funcall(value, rb_intern("class"), 0);
680
+ if (klass == rb_cTime || klass == rb_cDateTime)
681
+ {
682
+ if (klass == rb_cDateTime)
623
683
  {
624
- if (klass == rb_cDateTime)
625
- {
626
- value = rb_funcall(value, rb_intern("to_time"), 0);
627
- }
628
- value = rb_funcall(value, rb_intern("to_f"), 0);
629
- return scope.Escape(Date::New(isolate, NUM2DBL(value) * 1000));
684
+ value = rb_funcall(value, rb_intern("to_time"), 0);
630
685
  }
686
+ value = rb_funcall(value, rb_intern("to_f"), 0);
687
+ return scope.Escape(Date::New(context, NUM2DBL(value) * 1000).ToLocalChecked());
688
+ }
631
689
  // break intentionally missing
632
690
  }
633
- case T_OBJECT:
634
- case T_CLASS:
635
- case T_ICLASS:
636
- case T_MODULE:
637
- case T_REGEXP:
638
- case T_MATCH:
639
- case T_STRUCT:
640
- case T_BIGNUM:
641
- case T_FILE:
642
- case T_UNDEF:
643
- case T_NODE:
644
- default:
691
+ case T_OBJECT:
692
+ case T_CLASS:
693
+ case T_ICLASS:
694
+ case T_MODULE:
695
+ case T_REGEXP:
696
+ case T_MATCH:
697
+ case T_STRUCT:
698
+ case T_BIGNUM:
699
+ case T_FILE:
700
+ case T_UNDEF:
701
+ case T_NODE:
702
+ default:
645
703
  {
646
704
  if (rb_respond_to(value, rb_intern("to_s"))) {
647
705
  // TODO: if this throws we're screwed
648
706
  value = rb_funcall(value, rb_intern("to_s"), 0);
649
707
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
650
708
  }
651
- return scope.Escape(String::NewFromUtf8(isolate, "Undefined Conversion"));
709
+ return scope.Escape(
710
+ String::NewFromUtf8Literal(isolate, "Undefined Conversion"));
652
711
  }
653
712
  }
654
-
655
713
  }
656
714
 
657
715
  static void unblock_eval(void *ptr) {
@@ -659,6 +717,81 @@ static void unblock_eval(void *ptr) {
659
717
  eval->context_info->isolate_info->interrupted = true;
660
718
  }
661
719
 
720
+ /*
721
+ * The implementations of the run_extra_code(), create_snapshot_data_blob() and
722
+ * warm_up_snapshot_data_blob() functions have been derived from V8's test suite.
723
+ */
724
+ static bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
725
+ const char *utf8_source, const char *name) {
726
+ Context::Scope context_scope(context);
727
+ TryCatch try_catch(isolate);
728
+ Local<String> source_string;
729
+ if (!String::NewFromUtf8(isolate, utf8_source).ToLocal(&source_string)) {
730
+ return false;
731
+ }
732
+ Local<String> resource_name =
733
+ String::NewFromUtf8(isolate, name).ToLocalChecked();
734
+ ScriptOrigin origin(resource_name);
735
+ ScriptCompiler::Source source(source_string, origin);
736
+ Local<Script> script;
737
+ if (!ScriptCompiler::Compile(context, &source).ToLocal(&script))
738
+ return false;
739
+ if (script->Run(context).IsEmpty()) return false;
740
+ return true;
741
+ }
742
+
743
+ static StartupData
744
+ create_snapshot_data_blob(const char *embedded_source = nullptr) {
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);
749
+ {
750
+ HandleScope scope(isolate);
751
+ Local<v8::Context> context = v8::Context::New(isolate);
752
+ if (embedded_source != nullptr &&
753
+ !run_extra_code(isolate, context, embedded_source, "<embedded>")) {
754
+ return {};
755
+ }
756
+ snapshot_creator.SetDefaultContext(context);
757
+ }
758
+ return snapshot_creator.CreateBlob(
759
+ SnapshotCreator::FunctionCodeHandling::kClear);
760
+ }
761
+
762
+ StartupData warm_up_snapshot_data_blob(StartupData cold_snapshot_blob,
763
+ const char *warmup_source) {
764
+ // Use following steps to create a warmed up snapshot blob from a cold one:
765
+ // - Create a new isolate from the cold snapshot.
766
+ // - Create a new context to run the warmup script. This will trigger
767
+ // compilation of executed functions.
768
+ // - Create a new context. This context will be unpolluted.
769
+ // - Serialize the isolate and the second context into a new snapshot blob.
770
+ StartupData result = {nullptr, 0};
771
+
772
+ if (cold_snapshot_blob.raw_size > 0 && cold_snapshot_blob.data != nullptr &&
773
+ warmup_source != NULL) {
774
+ SnapshotCreator snapshot_creator(nullptr, &cold_snapshot_blob);
775
+ Isolate *isolate = snapshot_creator.GetIsolate();
776
+ {
777
+ HandleScope scope(isolate);
778
+ Local<Context> context = Context::New(isolate);
779
+ if (!run_extra_code(isolate, context, warmup_source, "<warm-up>")) {
780
+ return result;
781
+ }
782
+ }
783
+ {
784
+ HandleScope handle_scope(isolate);
785
+ isolate->ContextDisposedNotification(false);
786
+ Local<Context> context = Context::New(isolate);
787
+ snapshot_creator.SetDefaultContext(context);
788
+ }
789
+ result = snapshot_creator.CreateBlob(
790
+ SnapshotCreator::FunctionCodeHandling::kKeep);
791
+ }
792
+ return result;
793
+ }
794
+
662
795
  static VALUE rb_snapshot_size(VALUE self, VALUE str) {
663
796
  SnapshotInfo* snapshot_info;
664
797
  Data_Get_Struct(self, SnapshotInfo, snapshot_info);
@@ -677,7 +810,7 @@ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
677
810
 
678
811
  init_v8();
679
812
 
680
- StartupData startup_data = V8::CreateSnapshotDataBlob(RSTRING_PTR(str));
813
+ StartupData startup_data = create_snapshot_data_blob(RSTRING_PTR(str));
681
814
 
682
815
  if (startup_data.data == NULL && startup_data.raw_size == 0) {
683
816
  rb_raise(rb_eSnapshotError, "Could not create snapshot, most likely the source is incorrect");
@@ -708,7 +841,7 @@ static VALUE rb_snapshot_warmup_unsafe(VALUE self, VALUE str) {
708
841
  init_v8();
709
842
 
710
843
  StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
711
- StartupData warm_startup_data = V8::WarmUpSnapshotDataBlob(cold_startup_data, RSTRING_PTR(str));
844
+ StartupData warm_startup_data = warm_up_snapshot_data_blob(cold_startup_data, RSTRING_PTR(str));
712
845
 
713
846
  if (warm_startup_data.data == NULL && warm_startup_data.raw_size == 0) {
714
847
  rb_raise(rb_eSnapshotError, "Could not warm up snapshot, most likely the source is incorrect");
@@ -771,6 +904,29 @@ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
771
904
  return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
772
905
  }
773
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
+
774
930
  static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
775
931
  ContextInfo* context_info;
776
932
  Data_Get_Struct(self, ContextInfo, context_info);
@@ -887,8 +1043,8 @@ static VALUE convert_result_to_ruby(VALUE self /* context */,
887
1043
  Local<Value> tmp = Local<Value>::New(isolate, *result.value);
888
1044
 
889
1045
  if (result.json) {
890
- Local<String> rstr = tmp->ToString();
891
- VALUE json_string = rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(), rb_enc_find("utf-8"));
1046
+ Local<String> rstr = tmp->ToString(p_ctx->Get(isolate)).ToLocalChecked();
1047
+ VALUE json_string = rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
892
1048
  ret = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
893
1049
  } else {
894
1050
  ret = convert_v8_to_ruby(isolate, *p_ctx, tmp);
@@ -1009,6 +1165,8 @@ gvl_ruby_callback(void* data) {
1009
1165
  VALUE result;
1010
1166
  VALUE self;
1011
1167
  VALUE parent;
1168
+ ContextInfo* context_info;
1169
+
1012
1170
  {
1013
1171
  HandleScope scope(args->GetIsolate());
1014
1172
  Local<External> external = Local<External>::Cast(args->Data());
@@ -1021,7 +1179,6 @@ gvl_ruby_callback(void* data) {
1021
1179
  return NULL;
1022
1180
  }
1023
1181
 
1024
- ContextInfo* context_info;
1025
1182
  Data_Get_Struct(parent, ContextInfo, context_info);
1026
1183
 
1027
1184
  if (length > 0) {
@@ -1031,7 +1188,7 @@ gvl_ruby_callback(void* data) {
1031
1188
  for (int i = 0; i < length; i++) {
1032
1189
  Local<Value> value = ((*args)[i]).As<Value>();
1033
1190
  VALUE tmp = convert_v8_to_ruby(args->GetIsolate(),
1034
- *context_info->context, value);
1191
+ *context_info->context, value);
1035
1192
  rb_ary_push(ruby_args, tmp);
1036
1193
  }
1037
1194
  }
@@ -1044,7 +1201,9 @@ gvl_ruby_callback(void* data) {
1044
1201
  callback_data.failed = false;
1045
1202
 
1046
1203
  if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
1047
- 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"));
1048
1207
  args->GetIsolate()->TerminateExecution();
1049
1208
  if (length > 0) {
1050
1209
  rb_ary_clear(ruby_args);
@@ -1058,11 +1217,11 @@ gvl_ruby_callback(void* data) {
1058
1217
 
1059
1218
  if(callback_data.failed) {
1060
1219
  rb_iv_set(parent, "@current_exception", result);
1061
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
1220
+ args->GetIsolate()->ThrowException(String::NewFromUtf8Literal(args->GetIsolate(), "Ruby exception"));
1062
1221
  }
1063
1222
  else {
1064
1223
  HandleScope scope(args->GetIsolate());
1065
- Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), result);
1224
+ Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), context_info->context->Get(args->GetIsolate()), result);
1066
1225
  args->GetReturnValue().Set(v8_result);
1067
1226
  }
1068
1227
 
@@ -1119,8 +1278,10 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1119
1278
  Local<Context> context = context_info->context->Get(isolate);
1120
1279
  Context::Scope context_scope(context);
1121
1280
 
1122
- Local<String> v8_str = String::NewFromUtf8(isolate, RSTRING_PTR(name),
1123
- NewStringType::kNormal, (int)RSTRING_LEN(name)).ToLocalChecked();
1281
+ Local<String> v8_str =
1282
+ String::NewFromUtf8(isolate, RSTRING_PTR(name),
1283
+ NewStringType::kNormal, (int)RSTRING_LEN(name))
1284
+ .ToLocalChecked();
1124
1285
 
1125
1286
  // copy self so we can access from v8 external
1126
1287
  VALUE* self_copy;
@@ -1130,24 +1291,38 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1130
1291
  Local<Value> external = External::New(isolate, self_copy);
1131
1292
 
1132
1293
  if (parent_object == Qnil) {
1133
- context->Global()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
1134
- } else {
1294
+ context->Global()->Set(
1295
+ context,
1296
+ v8_str,
1297
+ FunctionTemplate::New(isolate, ruby_callback, external)
1298
+ ->GetFunction(context)
1299
+ .ToLocalChecked());
1135
1300
 
1136
- Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
1137
- NewStringType::kNormal, (int)RSTRING_LEN(parent_object_eval)).ToLocalChecked();
1301
+ } else {
1302
+ Local<String> eval =
1303
+ String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
1304
+ NewStringType::kNormal,
1305
+ (int)RSTRING_LEN(parent_object_eval))
1306
+ .ToLocalChecked();
1138
1307
 
1139
1308
  MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
1140
1309
  if (parsed_script.IsEmpty()) {
1141
- parse_error = true;
1310
+ parse_error = true;
1142
1311
  } else {
1143
- MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
1312
+ MaybeLocal<Value> maybe_value =
1313
+ parsed_script.ToLocalChecked()->Run(context);
1144
1314
  attach_error = true;
1145
1315
 
1146
1316
  if (!maybe_value.IsEmpty()) {
1147
1317
  Local<Value> value = maybe_value.ToLocalChecked();
1148
- if (value->IsObject()){
1149
- value.As<Object>()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
1150
- attach_error = false;
1318
+ if (value->IsObject()) {
1319
+ value.As<Object>()->Set(
1320
+ context,
1321
+ v8_str,
1322
+ FunctionTemplate::New(isolate, ruby_callback, external)
1323
+ ->GetFunction(context)
1324
+ .ToLocalChecked());
1325
+ attach_error = false;
1151
1326
  }
1152
1327
  }
1153
1328
  }
@@ -1177,35 +1352,39 @@ static VALUE rb_context_isolate_mutex(VALUE self) {
1177
1352
  return context_info->isolate_info->mutex;
1178
1353
  }
1179
1354
 
1180
- void free_isolate(IsolateInfo* isolate_info) {
1181
-
1182
- if (isolate_info->isolate) {
1183
- Locker lock(isolate_info->isolate);
1184
- }
1185
-
1186
- if (isolate_info->isolate) {
1187
- if (isolate_info->interrupted) {
1188
- 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");
1189
1361
  } else {
1190
-
1191
- if (isolate_info->pid != getpid()) {
1192
- 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
+ );
1193
1372
  } else {
1194
- isolate_info->isolate->Dispose();
1373
+ isolate->Dispose();
1195
1374
  }
1196
1375
  }
1197
- isolate_info->isolate = NULL;
1376
+ isolate = nullptr;
1198
1377
  }
1199
1378
 
1200
- if (isolate_info->startup_data) {
1201
- delete[] isolate_info->startup_data->data;
1202
- delete isolate_info->startup_data;
1379
+ if (startup_data) {
1380
+ delete[] startup_data->data;
1381
+ delete startup_data;
1203
1382
  }
1204
1383
 
1205
- delete isolate_info->allocator;
1384
+ delete allocator;
1206
1385
  }
1207
1386
 
1208
- static void *free_context_raw(void* arg) {
1387
+ static void free_context_raw(void *arg) {
1209
1388
  ContextInfo* context_info = (ContextInfo*)arg;
1210
1389
  IsolateInfo* isolate_info = context_info->isolate_info;
1211
1390
  Persistent<Context>* context = context_info->context;
@@ -1222,6 +1401,20 @@ static void *free_context_raw(void* arg) {
1222
1401
  }
1223
1402
 
1224
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
+
1225
1418
  return NULL;
1226
1419
  }
1227
1420
 
@@ -1236,22 +1429,17 @@ static void free_context(ContextInfo* context_info) {
1236
1429
 
1237
1430
  if (isolate_info && isolate_info->refs() > 1) {
1238
1431
  pthread_t free_context_thread;
1239
- 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)) {
1240
1434
  fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
1241
1435
  }
1242
-
1243
1436
  } else {
1244
1437
  free_context_raw(context_info_copy);
1245
1438
  }
1246
1439
 
1247
- if (context_info->context && isolate_info && isolate_info->isolate) {
1248
1440
  context_info->context = NULL;
1249
- }
1250
-
1251
- if (isolate_info) {
1252
1441
  context_info->isolate_info = NULL;
1253
1442
  }
1254
- }
1255
1443
 
1256
1444
  static void deallocate_isolate(void* data) {
1257
1445
 
@@ -1265,7 +1453,7 @@ static void mark_isolate(void* data) {
1265
1453
  isolate_info->mark();
1266
1454
  }
1267
1455
 
1268
- void deallocate(void* data) {
1456
+ static void deallocate(void* data) {
1269
1457
  ContextInfo* context_info = (ContextInfo*)data;
1270
1458
 
1271
1459
  free_context(context_info);
@@ -1280,22 +1468,22 @@ static void mark_context(void* data) {
1280
1468
  }
1281
1469
  }
1282
1470
 
1283
- void deallocate_external_function(void * data) {
1471
+ static void deallocate_external_function(void * data) {
1284
1472
  xfree(data);
1285
1473
  }
1286
1474
 
1287
- void deallocate_snapshot(void * data) {
1475
+ static void deallocate_snapshot(void * data) {
1288
1476
  SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
1289
1477
  delete[] snapshot_info->data;
1290
1478
  xfree(snapshot_info);
1291
1479
  }
1292
1480
 
1293
- VALUE allocate_external_function(VALUE klass) {
1481
+ static VALUE allocate_external_function(VALUE klass) {
1294
1482
  VALUE* self = ALLOC(VALUE);
1295
1483
  return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
1296
1484
  }
1297
1485
 
1298
- VALUE allocate(VALUE klass) {
1486
+ static VALUE allocate(VALUE klass) {
1299
1487
  ContextInfo* context_info = ALLOC(ContextInfo);
1300
1488
  context_info->isolate_info = NULL;
1301
1489
  context_info->context = NULL;
@@ -1303,7 +1491,7 @@ VALUE allocate(VALUE klass) {
1303
1491
  return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
1304
1492
  }
1305
1493
 
1306
- VALUE allocate_snapshot(VALUE klass) {
1494
+ static VALUE allocate_snapshot(VALUE klass) {
1307
1495
  SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
1308
1496
  snapshot_info->data = NULL;
1309
1497
  snapshot_info->raw_size = 0;
@@ -1311,7 +1499,7 @@ VALUE allocate_snapshot(VALUE klass) {
1311
1499
  return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
1312
1500
  }
1313
1501
 
1314
- VALUE allocate_isolate(VALUE klass) {
1502
+ static VALUE allocate_isolate(VALUE klass) {
1315
1503
  IsolateInfo* isolate_info = new IsolateInfo();
1316
1504
 
1317
1505
  return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
@@ -1350,6 +1538,71 @@ rb_heap_stats(VALUE self) {
1350
1538
  return rval;
1351
1539
  }
1352
1540
 
1541
+ // https://github.com/bnoordhuis/node-heapdump/blob/master/src/heapdump.cc
1542
+ class FileOutputStream : public OutputStream {
1543
+ public:
1544
+ FileOutputStream(FILE* stream) : stream_(stream) {}
1545
+
1546
+ virtual int GetChunkSize() {
1547
+ return 65536;
1548
+ }
1549
+
1550
+ virtual void EndOfStream() {}
1551
+
1552
+ virtual WriteResult WriteAsciiChunk(char* data, int size) {
1553
+ const size_t len = static_cast<size_t>(size);
1554
+ size_t off = 0;
1555
+
1556
+ while (off < len && !feof(stream_) && !ferror(stream_))
1557
+ off += fwrite(data + off, 1, len - off, stream_);
1558
+
1559
+ return off == len ? kContinue : kAbort;
1560
+ }
1561
+
1562
+ private:
1563
+ FILE* stream_;
1564
+ };
1565
+
1566
+
1567
+ static VALUE
1568
+ rb_heap_snapshot(VALUE self, VALUE file) {
1569
+
1570
+ rb_io_t *fptr;
1571
+
1572
+ fptr = RFILE(file)->fptr;
1573
+
1574
+ if (!fptr) return Qfalse;
1575
+
1576
+ FILE* fp;
1577
+ fp = fdopen(fptr->fd, "w");
1578
+ if (fp == NULL) return Qfalse;
1579
+
1580
+
1581
+ ContextInfo* context_info;
1582
+ Data_Get_Struct(self, ContextInfo, context_info);
1583
+ Isolate* isolate;
1584
+ isolate = context_info->isolate_info ? context_info->isolate_info->isolate : NULL;
1585
+
1586
+ if (!isolate) return Qfalse;
1587
+
1588
+ Locker lock(isolate);
1589
+ Isolate::Scope isolate_scope(isolate);
1590
+ HandleScope handle_scope(isolate);
1591
+
1592
+ HeapProfiler* heap_profiler = isolate->GetHeapProfiler();
1593
+
1594
+ const HeapSnapshot* const snap = heap_profiler->TakeHeapSnapshot();
1595
+
1596
+ FileOutputStream stream(fp);
1597
+ snap->Serialize(&stream, HeapSnapshot::kJSON);
1598
+
1599
+ fflush(fp);
1600
+
1601
+ const_cast<HeapSnapshot*>(snap)->Delete();
1602
+
1603
+ return Qtrue;
1604
+ }
1605
+
1353
1606
  static VALUE
1354
1607
  rb_context_stop(VALUE self) {
1355
1608
 
@@ -1402,13 +1655,23 @@ nogvl_context_call(void *args) {
1402
1655
  if (!call) {
1403
1656
  return 0;
1404
1657
  }
1405
- Isolate* isolate = call->context_info->isolate_info->isolate;
1658
+ IsolateInfo *isolate_info = call->context_info->isolate_info;
1659
+ Isolate* isolate = isolate_info->isolate;
1406
1660
 
1407
1661
  // in gvl flag
1408
1662
  isolate->SetData(IN_GVL, (void*)false);
1409
1663
  // terminate ASAP
1410
1664
  isolate->SetData(DO_TERMINATE, (void*)false);
1411
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
+
1412
1675
  Isolate::Scope isolate_scope(isolate);
1413
1676
  EscapableHandleScope handle_scope(isolate);
1414
1677
  TryCatch trycatch(isolate);
@@ -1466,6 +1729,13 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1466
1729
  call_argv = argv + 1;
1467
1730
  }
1468
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
+
1469
1739
  bool missingFunction = false;
1470
1740
  {
1471
1741
  Locker lock(isolate);
@@ -1477,8 +1747,11 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1477
1747
 
1478
1748
  // examples of such usage can be found in
1479
1749
  // https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
1480
- Local<String> fname = String::NewFromUtf8(isolate, call.function_name);
1481
- 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
+ }
1482
1755
 
1483
1756
  if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1484
1757
  missingFunction = true;
@@ -1494,7 +1767,7 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1494
1767
  return Qnil;
1495
1768
  }
1496
1769
  for(int i=0; i < fun_argc; i++) {
1497
- call.argv[i] = convert_ruby_to_v8(isolate, call_argv[i]);
1770
+ call.argv[i] = convert_ruby_to_v8(isolate, context, call_argv[i]);
1498
1771
  }
1499
1772
  }
1500
1773
  #if RUBY_API_VERSION_MAJOR > 1
@@ -1527,12 +1800,44 @@ static VALUE rb_context_create_isolate_value(VALUE self) {
1527
1800
  return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
1528
1801
  }
1529
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
+
1530
1824
  extern "C" {
1531
1825
 
1532
- void Init_sq_mini_racer_extension ( void )
1826
+ __attribute__((visibility("default"))) void Init_sq_mini_racer_extension ( void )
1533
1827
  {
1534
- 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
+ }
1535
1839
  VALUE rb_mMiniRacer = rb_define_module_under(rb_mSqreen, "MiniRacer");
1840
+ rb_define_module_function(rb_mMiniRacer, "monotime", (VALUE(*)(...))&rb_monotime, 0);
1536
1841
  rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1537
1842
  rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
1538
1843
  rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
@@ -1558,6 +1863,8 @@ extern "C" {
1558
1863
  rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
1559
1864
  rb_define_method(rb_cContext, "low_memory_notification", (VALUE(*)(...))&rb_context_low_memory_notification, 0);
1560
1865
  rb_define_method(rb_cContext, "heap_stats", (VALUE(*)(...))&rb_heap_stats, 0);
1866
+ rb_define_method(rb_cContext, "write_heap_snapshot_unsafe", (VALUE(*)(...))&rb_heap_snapshot, 1);
1867
+
1561
1868
  rb_define_private_method(rb_cContext, "create_isolate_value",(VALUE(*)(...))&rb_context_create_isolate_value, 0);
1562
1869
  rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
1563
1870
  rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
@@ -1577,8 +1884,19 @@ extern "C" {
1577
1884
  rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1578
1885
 
1579
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);
1580
1889
  rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
1581
1890
 
1582
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
+ }
1583
1901
  }
1584
1902
  }