sq_mini_racer 0.2.4.sqreen2 → 0.3.1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,33 +1,232 @@
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_version
33
+ '14.14.0.0.beta2'
34
+ end
35
+
36
+ def libv8_basename
37
+ "#{libv8_gem_name}-#{libv8_version}-#{ruby_platform}"
38
+ end
39
+
40
+ def libv8_gemspec_no_libc
41
+ platform_no_libc = ruby_platform.to_s.split('-')[0..1].join('-')
42
+ "#{libv8_gem_name}-#{libv8_version}-#{platform_no_libc}.gemspec"
43
+ end
44
+
45
+ def libv8_gemspec
46
+ "#{libv8_basename}.gemspec"
47
+ end
48
+
49
+ def libv8_local_path(path=Gem.path)
50
+ gemspecs = [libv8_gemspec, libv8_gemspec_no_libc].uniq
51
+ puts "looking for #{gemspecs.join(', ')} in installed gems"
52
+ candidates = path.product(gemspecs)
53
+ .map { |(p, gemspec)| File.join(p, 'specifications', gemspec) }
54
+ p candidates
55
+ found = candidates.select { |f| File.exist?(f) }.first
56
+
57
+ unless found
58
+ puts "#{gemspecs.join(', ')} not found in installed gems"
59
+ return
60
+ end
61
+
62
+ puts "found in installed specs: #{found}"
63
+
64
+ gemdir = File.basename(found, '.gemspec')
65
+ dir = File.expand_path(File.join(found, '..', '..', 'gems', gemdir))
66
+
67
+ unless Dir.exist?(dir)
68
+ puts "not found in installed gems: #{dir}"
69
+ return
70
+ end
71
+
72
+ puts "found in installed gems: #{dir}"
73
+
74
+ dir
75
+ end
76
+
77
+ def vendor_path
78
+ File.join(Dir.pwd, 'vendor')
79
+ end
80
+
81
+ def libv8_vendor_path
82
+ puts "looking for #{libv8_basename} in #{vendor_path}"
83
+ path = Dir.glob("#{vendor_path}/#{libv8_basename}").first
84
+
85
+ unless path
86
+ puts "#{libv8_basename} not found in #{vendor_path}"
87
+ return
88
+ end
89
+
90
+ puts "looking for #{libv8_basename}/lib/libv8-node.rb in #{vendor_path}"
91
+ unless Dir.glob(File.join(vendor_path, libv8_basename, 'lib', 'libv8-node.rb')).first
92
+ puts "#{libv8_basename}/lib/libv8.rb not found in #{vendor_path}"
93
+ return
94
+ end
95
+
96
+ path
97
+ end
98
+
99
+ def parse_platform(str)
100
+ Gem::Platform.new(str).tap do |p|
101
+ p.instance_eval { @cpu = 'x86_64' } if str =~ /universal.*darwin/
102
+ end
103
+ end
104
+
105
+ def ruby_platform
106
+ parse_platform(RUBY_PLATFORM)
107
+ end
108
+
109
+ def http_get(uri)
110
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
111
+ res = http.get(uri.path)
112
+
113
+ abort("HTTP error #{res.code}: #{uri}") unless res.code == '200'
114
+
115
+ return res.body
116
+ end
117
+ end
118
+
119
+ def libv8_remote_search
120
+ body = http_get(URI("https://rubygems.org/api/v1/versions/#{libv8_gem_name}.json"))
121
+ json = JSON.parse(body)
122
+
123
+ versions = json.select do |v|
124
+ Gem::Version.new(v['number']) == Gem::Version.new(libv8_version)
125
+ end
126
+ abort(<<-ERROR) if versions.empty?
127
+ ERROR: could not find #{libv8_gem_name} (version #{libv8_version}) in rubygems.org
128
+ ERROR
129
+
130
+ platform_versions = versions.select do |v|
131
+ parse_platform(v['platform']) == ruby_platform unless v['platform'] =~ /universal.*darwin/
132
+ end
133
+ abort(<<-ERROR) if platform_versions.empty?
134
+ ERROR: found #{libv8_gem_name}-#{libv8_version}, but no binary for #{ruby_platform}
135
+ try "gem install #{libv8_gem_name} -v '#{libv8_version}'" to attempt to build libv8 from source
136
+ ERROR
137
+
138
+ platform_versions.first
139
+ end
140
+
141
+ def libv8_download_uri(name, version, platform)
142
+ URI("https://rubygems.org/downloads/#{name}-#{version}-#{platform}.gem")
143
+ end
144
+
145
+ def libv8_downloaded_gem(name, version, platform)
146
+ "#{name}-#{version}-#{platform}.gem"
147
+ end
148
+
149
+ def libv8_download(name, version, platform)
150
+ FileUtils.mkdir_p(vendor_path)
151
+ body = http_get(libv8_download_uri(name, version, platform))
152
+ File.open(File.join(vendor_path, libv8_downloaded_gem(name, version, platform)), 'wb') { |f| f.write(body) }
153
+ end
154
+
155
+ def libv8_install!
156
+ cmd = "gem install #{libv8_gem_name} --version '#{libv8_version}' --install-dir '#{vendor_path}'"
157
+ puts "installing #{libv8_gem_name} using `#{cmd}`"
158
+ rc = system(cmd)
159
+
160
+ abort(<<-ERROR) unless rc
161
+ ERROR: could not install #{libv8_gem_name} #{libv8_version}
162
+ try "gem install #{libv8_gem_name} -v '#{libv8_version}'" to attempt to build libv8 from source
163
+ ERROR
164
+
165
+ libv8_local_path([vendor_path])
166
+ end
167
+
168
+ def libv8_vendor!
169
+ return libv8_install! if Gem::VERSION < '2.0'
170
+
171
+ version = libv8_remote_search
172
+
173
+ puts "downloading #{libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform'])} to #{vendor_path}"
174
+ libv8_download(libv8_gem_name, version['number'], version['platform'])
175
+
176
+ package = Gem::Package.new(File.join(vendor_path, libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform'])))
177
+ package.extract_files(File.join(vendor_path, File.basename(libv8_downloaded_gem(libv8_gem_name, version['number'], version['platform']), '.gem')))
178
+
179
+ libv8_vendor_path
180
+ end
181
+
182
+ def ensure_libv8_load_path
183
+ puts "detected platform #{RUBY_PLATFORM} => #{ruby_platform}"
184
+
185
+ libv8_path = libv8_local_path
186
+ unless ENV['ONLY_INSTALLED_LIBV8_GEM']
187
+ libv8_path ||= libv8_vendor_path || libv8_vendor!
188
+ end
189
+
190
+ abort(<<-ERROR) unless libv8_path
191
+ ERROR: could not find #{libv8_gem_name}
192
+ ERROR
193
+
194
+ $LOAD_PATH.unshift(File.join(libv8_path, 'ext'))
195
+ $LOAD_PATH.unshift(File.join(libv8_path, 'lib'))
196
+ end
197
+
198
+ ensure_libv8_load_path
199
+
200
+ require 'libv8-node'
201
+
202
+ IS_DARWIN = RUBY_PLATFORM =~ /darwin/
6
203
 
7
204
  have_library('pthread')
8
205
  have_library('objc') if IS_DARWIN
9
- $CPPFLAGS.gsub! /-std=[^\s]+/, ''
206
+ cppflags_clear_std!
10
207
  $CPPFLAGS += " -Wall" unless $CPPFLAGS.split.include? "-Wall"
11
208
  $CPPFLAGS += " -g" unless $CPPFLAGS.split.include? "-g"
12
209
  $CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic"
13
210
  $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or IS_DARWIN
14
211
  $CPPFLAGS += " -std=c++0x"
15
212
  $CPPFLAGS += " -fpermissive"
16
- $CPPFLAGS += " -fno-omit-frame-pointer"
17
- if enable_config('avx2')
18
- $CPPFLAGS += " -mavx2"
19
- else
20
- $CPPFLAGS += " -mssse3"
21
- end
213
+ $CPPFLAGS += " -DV8_COMPRESS_POINTERS"
214
+ $CPPFLAGS += " -fvisibility=hidden "
215
+ cppflags_add_frame_pointer!
216
+ cppflags_add_cpu_extension!
22
217
 
23
218
  $CPPFLAGS += " -Wno-reserved-user-defined-literal" if IS_DARWIN
24
219
 
25
220
  $LDFLAGS.insert(0, " -stdlib=libc++ ") if IS_DARWIN
221
+ $LDFLAGS += " -Wl,--no-undefined " unless IS_DARWIN
26
222
 
27
223
  if ENV['CXX']
28
224
  puts "SETTING CXX"
29
225
  CONFIG['CXX'] = ENV['CXX']
30
226
  end
227
+ # 1.9 has no $CXXFLAGS
228
+ $CPPFLAGS += " #{ENV['CPPFLAGS']}" if ENV['CPPFLAGS']
229
+ $LDFLAGS += " #{ENV['LDFLAGS']}" if ENV['LDFLAGS']
31
230
 
32
231
  CXX11_TEST = <<EOS
33
232
  #if __cplusplus <= 199711L
@@ -57,94 +256,15 @@ if CONFIG['warnflags']
57
256
  CONFIG['warnflags'].gsub!('-Wimplicit-function-declaration', '')
58
257
  end
59
258
 
60
- if enable_config('debug')
259
+ if enable_config('debug') || enable_config('asan')
61
260
  CONFIG['debugflags'] << ' -ggdb3 -O0'
62
261
  end
63
262
 
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
- # 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
- def force_platform_gem
89
- gem_version = `gem --version`
90
- return 'gem' unless $?.success?
91
-
92
- return 'gem' if gem_version.to_f.zero? || gem_version.to_f >= 2.3
93
- return 'gem' if RUBY_PLATFORM != 'x86_64-linux'
94
-
95
- gem_binary = `which gem`
96
- return 'gem' unless $?.success?
97
-
98
- ruby = File.foreach(gem_binary.strip).first.sub(/^#!/, '').strip
99
- unless File.file? ruby
100
- warn "No valid ruby: #{ruby}"
101
- return 'gem'
102
- end
103
-
104
- require 'tempfile'
105
- file = Tempfile.new('sq_mini_racer')
106
- file << <<EOS
107
- require 'rubygems'
108
- Gem.platforms.reject! { |it| it == 'ruby' }
109
- #{IO.read(gem_binary.strip)}
110
- EOS
111
- file.close
112
- "#{ruby} '#{file.path}'"
113
- end
114
-
115
- LIBV8_VERSION = '6.7.288.46.1'
116
- libv8_rb = Dir.glob('**/libv8.rb').first
117
- FileUtils.mkdir_p('gemdir')
118
- unless libv8_rb
119
- gem_name = libv8_gem_name
120
- cmd = "#{fixup_libtinfo} #{force_platform_gem} install --version '= #{LIBV8_VERSION}' --install-dir gemdir #{gem_name}"
121
- puts "Will try downloading #{gem_name} gem: #{cmd}"
122
- `#{cmd}`
123
- unless $?.success?
124
- warn <<EOS
125
-
126
- WARNING: Could not download a private copy of the libv8 gem. Please make
127
- sure that you have internet access and that the `gem` binary is available.
128
-
129
- EOS
130
- end
131
-
132
- libv8_rb = Dir.glob('**/libv8.rb').first
133
- unless libv8_rb
134
- warn <<EOS
135
-
136
- WARNING: Could not find libv8 after the local copy of libv8 having supposedly
137
- been installed.
138
-
139
- EOS
140
- end
141
- end
263
+ Libv8::Node.configure_makefile
142
264
 
143
- if libv8_rb
144
- $:.unshift(File.dirname(libv8_rb) + '/../ext')
145
- $:.unshift File.dirname(libv8_rb)
265
+ if enable_config('asan')
266
+ $CPPFLAGS.insert(0, " -fsanitize=address ")
267
+ $LDFLAGS.insert(0, " -fsanitize=address ")
146
268
  end
147
269
 
148
- require 'libv8'
149
- Libv8.configure_makefile
150
270
  create_makefile 'sq_mini_racer_extension'
@@ -8,12 +8,24 @@
8
8
  # undef HAVE_BUILTIN___BUILTIN_CHOOSE_EXPR_CONSTANT_P
9
9
  # undef HAVE_BUILTIN___BUILTIN_TYPES_COMPATIBLE_P
10
10
 
11
- #include <ruby.h>
12
11
  #include <ruby/version.h>
12
+
13
+ // workaround for Ruby 2.3.0 on macOS
14
+ #if RUBY_API_VERSION_CODE == 20300
15
+ #ifdef _DARWIN_C_SOURCE
16
+ #ifndef __STDC_LIB_EXT1__
17
+ #undef HAVE_MEMSET_S
18
+ #endif
19
+ #endif
20
+ #endif
21
+
22
+ #include <ruby.h>
13
23
  #if RUBY_API_VERSION_MAJOR > 1
14
24
  #include <ruby/thread.h>
15
25
  #endif
26
+ #include <ruby/io.h>
16
27
  #include <v8.h>
28
+ #include <v8-profiler.h>
17
29
  #include <libplatform/libplatform.h>
18
30
  #include <ruby/encoding.h>
19
31
  #include <pthread.h>
@@ -24,6 +36,8 @@
24
36
  #include "compat.hpp"
25
37
  #include "simdutf8check.h"
26
38
 
39
+ #include <time.h>
40
+
27
41
  using namespace v8;
28
42
 
29
43
  typedef struct {
@@ -37,6 +51,7 @@ public:
37
51
  ArrayBuffer::Allocator* allocator;
38
52
  StartupData* startup_data;
39
53
  bool interrupted;
54
+ bool added_gc_cb;
40
55
  pid_t pid;
41
56
  VALUE mutex;
42
57
 
@@ -54,15 +69,12 @@ public:
54
69
 
55
70
 
56
71
  IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
57
- interrupted(false), pid(getpid()), refs_count(0) {
72
+ interrupted(false), added_gc_cb(false), pid(getpid()), refs_count(0) {
58
73
  VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
59
74
  mutex = rb_class_new_instance(0, nullptr, cMutex);
60
75
  }
61
76
 
62
- ~IsolateInfo() {
63
- void free_isolate(IsolateInfo*);
64
- free_isolate(this);
65
- }
77
+ ~IsolateInfo();
66
78
 
67
79
  void init(SnapshotInfo* snapshot_info = nullptr);
68
80
 
@@ -139,6 +151,7 @@ typedef struct {
139
151
  Local<Function> fun;
140
152
  Local<Value> *argv;
141
153
  EvalResult result;
154
+ size_t max_memory;
142
155
  } FunctionCall;
143
156
 
144
157
  enum IsolateFlags {
@@ -164,9 +177,14 @@ static VALUE rb_mJSON;
164
177
  static VALUE rb_cFailedV8Conversion;
165
178
  static VALUE rb_cDateTime = Qnil;
166
179
 
167
- static Platform* current_platform = NULL;
180
+ static std::unique_ptr<Platform> current_platform = NULL;
168
181
  static std::mutex platform_lock;
169
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
+
170
188
  static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
171
189
  bool platform_already_initialized = false;
172
190
 
@@ -178,6 +196,9 @@ static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
178
196
  platform_lock.lock();
179
197
 
180
198
  if (current_platform == NULL) {
199
+ if (!strcmp(RSTRING_PTR(flag_as_str), "--single_threaded")) {
200
+ single_threaded = true;
201
+ }
181
202
  V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
182
203
  } else {
183
204
  platform_already_initialized = true;
@@ -201,8 +222,8 @@ static void init_v8() {
201
222
 
202
223
  if (current_platform == NULL) {
203
224
  V8::InitializeICU();
204
- current_platform = platform::CreateDefaultPlatform();
205
- V8::InitializePlatform(current_platform);
225
+ current_platform = platform::NewDefaultPlatform();
226
+ V8::InitializePlatform(current_platform.get());
206
227
  V8::Initialize();
207
228
  }
208
229
 
@@ -245,15 +266,17 @@ static void prepare_result(MaybeLocal<Value> v8res,
245
266
  if ((local_value->IsObject() || local_value->IsArray()) &&
246
267
  !local_value->IsDate() && !local_value->IsFunction()) {
247
268
  Local<Object> JSON = context->Global()->Get(
248
- String::NewFromUtf8(isolate, "JSON"))->ToObject();
269
+ context, String::NewFromUtf8Literal(isolate, "JSON"))
270
+ .ToLocalChecked().As<Object>();
249
271
 
250
- Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
251
- .As<Function>();
272
+ Local<Function> stringify = JSON->Get(
273
+ context, v8::String::NewFromUtf8Literal(isolate, "stringify"))
274
+ .ToLocalChecked().As<Function>();
252
275
 
253
- Local<Object> object = local_value->ToObject();
276
+ Local<Object> object = local_value->ToObject(context).ToLocalChecked();
254
277
  const unsigned argc = 1;
255
278
  Local<Value> argv[argc] = { object };
256
- MaybeLocal<Value> json = stringify->Call(JSON, argc, argv);
279
+ MaybeLocal<Value> json = stringify->Call(context, JSON, argc, argv);
257
280
 
258
281
  if (json.IsEmpty()) {
259
282
  evalRes.executed = false;
@@ -277,11 +300,21 @@ static void prepare_result(MaybeLocal<Value> v8res,
277
300
  evalRes.message = new Persistent<Value>();
278
301
  Local<Message> message = trycatch.Message();
279
302
  char buf[1000];
280
- 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
+
281
313
  len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(isolate, message->Get()),
282
- *String::Utf8Value(isolate, message->GetScriptResourceName()->ToString()),
283
- message->GetLineNumber(),
284
- message->GetStartColumn());
314
+ *String::Utf8Value(isolate, message->GetScriptResourceName()->ToString(context).ToLocalChecked()),
315
+ line,
316
+ column);
317
+
285
318
  if ((size_t) len >= sizeof(buf)) {
286
319
  len = sizeof(buf) - 1;
287
320
  buf[len] = '\0';
@@ -292,12 +325,13 @@ static void prepare_result(MaybeLocal<Value> v8res,
292
325
  } else if(trycatch.HasTerminated()) {
293
326
  evalRes.terminated = true;
294
327
  evalRes.message = new Persistent<Value>();
295
- 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)");
296
329
  evalRes.message->Reset(isolate, tmp);
297
330
  }
298
331
  if (!trycatch.StackTrace(context).IsEmpty()) {
299
332
  evalRes.backtrace = new Persistent<Value>();
300
- evalRes.backtrace->Reset(isolate, trycatch.StackTrace(context).ToLocalChecked()->ToString());
333
+ evalRes.backtrace->Reset(isolate,
334
+ trycatch.StackTrace(context).ToLocalChecked()->ToString(context).ToLocalChecked());
301
335
  }
302
336
  }
303
337
  }
@@ -312,7 +346,8 @@ nogvl_context_eval(void* arg) {
312
346
 
313
347
  EvalParams* eval_params = (EvalParams*)arg;
314
348
  EvalResult* result = eval_params->result;
315
- 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;
316
351
 
317
352
  Isolate::Scope isolate_scope(isolate);
318
353
  HandleScope handle_scope(isolate);
@@ -356,7 +391,10 @@ nogvl_context_eval(void* arg) {
356
391
  // parsing successful
357
392
  if (eval_params->max_memory > 0) {
358
393
  isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
394
+ if (!isolate_info->added_gc_cb) {
359
395
  isolate->AddGCEpilogueCallback(gc_callback);
396
+ isolate_info->added_gc_cb = true;
397
+ }
360
398
  }
361
399
 
362
400
  maybe_value = parsed_script.ToLocalChecked()->Run(context);
@@ -369,6 +407,12 @@ nogvl_context_eval(void* arg) {
369
407
  return 0;
370
408
  }
371
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
+
372
416
  // assumes isolate locking is in place
373
417
  static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
374
418
  Local<Value> value) {
@@ -381,11 +425,11 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
381
425
  }
382
426
 
383
427
  if (value->IsInt32()) {
384
- return INT2FIX(value->Int32Value());
428
+ return INT2FIX(value->Int32Value(context).ToChecked());
385
429
  }
386
430
 
387
431
  if (value->IsNumber()) {
388
- return rb_float_new(value->NumberValue());
432
+ return rb_float_new(value->NumberValue(context).ToChecked());
389
433
  }
390
434
 
391
435
  if (value->IsTrue()) {
@@ -400,8 +444,11 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
400
444
  VALUE rb_array = rb_ary_new();
401
445
  Local<Array> arr = Local<Array>::Cast(value);
402
446
  for(uint32_t i=0; i < arr->Length(); i++) {
403
- Local<Value> element = arr->Get(i);
404
- 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());
405
452
  if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
406
453
  return rb_elem;
407
454
  }
@@ -426,31 +473,52 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
426
473
  VALUE rb_hash = rb_hash_new();
427
474
  TryCatch trycatch(isolate);
428
475
 
429
- Local<Object> object = value->ToObject();
476
+ Local<Object> object = value->ToObject(context).ToLocalChecked();
430
477
  auto maybe_props = object->GetOwnPropertyNames(context);
431
478
  if (!maybe_props.IsEmpty()) {
432
479
  Local<Array> props = maybe_props.ToLocalChecked();
433
480
  for(uint32_t i=0; i < props->Length(); i++) {
434
- Local<Value> key = props->Get(i);
435
- VALUE rb_key = convert_v8_to_ruby(isolate, context, key);
436
- Local<Value> prop_value = object->Get(key);
437
- // 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());
438
486
 
439
- if (trycatch.HasCaught()) {
440
- // TODO isolate code that translates execption to ruby
441
- // exception so we can properly return it
442
- 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();
443
491
  }
444
492
 
445
- 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());
446
495
  rb_hash_aset(rb_hash, rb_key, rb_value);
447
496
  }
448
497
  }
449
498
  return rb_hash;
450
499
  }
451
500
 
452
- Local<String> rstr = value->ToString();
453
- 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
+ }
454
522
  }
455
523
 
456
524
  static VALUE convert_v8_to_ruby(Isolate* isolate,
@@ -547,7 +615,7 @@ treat_as_latin1:
547
615
  return v8str.ToLocalChecked();
548
616
  }
549
617
 
550
- 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)
551
619
  {
552
620
  EscapableHandleScope scope(isolate);
553
621
 
@@ -561,87 +629,87 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, VALUE value)
561
629
  VALUE klass;
562
630
 
563
631
  switch (TYPE(value)) {
564
- case T_FIXNUM:
632
+ case T_FIXNUM:
565
633
  {
566
- fixnum = NUM2LONG(value);
567
- if (fixnum > INT_MAX)
568
- {
569
- return scope.Escape(Number::New(isolate, (double)fixnum));
570
- }
571
- 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));
638
+ }
639
+ return scope.Escape(Integer::New(isolate, (int)fixnum));
572
640
  }
573
- case T_FLOAT:
574
- return scope.Escape(Number::New(isolate, NUM2DBL(value)));
575
- case T_STRING:
641
+ case T_FLOAT:
642
+ return scope.Escape(Number::New(isolate, NUM2DBL(value)));
643
+ case T_STRING:
576
644
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
577
- case T_NIL:
578
- return scope.Escape(Null(isolate));
579
- case T_TRUE:
580
- return scope.Escape(True(isolate));
581
- case T_FALSE:
582
- return scope.Escape(False(isolate));
583
- 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:
584
652
  {
585
- length = RARRAY_LEN(value);
586
- array = Array::New(isolate, (int)length);
587
- for(i=0; i<length; i++) {
588
- array->Set(i, convert_ruby_to_v8(isolate, rb_ary_entry(value, i)));
589
- }
590
- 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);
591
659
  }
592
- case T_HASH:
660
+ case T_HASH:
593
661
  {
594
- object = Object::New(isolate);
595
- hash_as_array = rb_funcall(value, rb_intern("to_a"), 0);
596
- length = RARRAY_LEN(hash_as_array);
597
- for(i=0; i<length; i++) {
598
- pair = rb_ary_entry(hash_as_array, i);
599
- object->Set(convert_ruby_to_v8(isolate, rb_ary_entry(pair, 0)),
600
- convert_ruby_to_v8(isolate, rb_ary_entry(pair, 1)));
601
- }
602
- 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);
603
671
  }
604
- case T_SYMBOL:
672
+ case T_SYMBOL:
605
673
  {
606
- value = rb_funcall(value, rb_intern("to_s"), 0);
674
+ value = rb_funcall(value, rb_intern("to_s"), 0);
607
675
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
608
676
  }
609
- case T_DATA:
677
+ case T_DATA:
610
678
  {
611
- klass = rb_funcall(value, rb_intern("class"), 0);
612
- 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)
613
683
  {
614
- if (klass == rb_cDateTime)
615
- {
616
- value = rb_funcall(value, rb_intern("to_time"), 0);
617
- }
618
- value = rb_funcall(value, rb_intern("to_f"), 0);
619
- return scope.Escape(Date::New(isolate, NUM2DBL(value) * 1000));
684
+ value = rb_funcall(value, rb_intern("to_time"), 0);
620
685
  }
686
+ value = rb_funcall(value, rb_intern("to_f"), 0);
687
+ return scope.Escape(Date::New(context, NUM2DBL(value) * 1000).ToLocalChecked());
688
+ }
621
689
  // break intentionally missing
622
690
  }
623
- case T_OBJECT:
624
- case T_CLASS:
625
- case T_ICLASS:
626
- case T_MODULE:
627
- case T_REGEXP:
628
- case T_MATCH:
629
- case T_STRUCT:
630
- case T_BIGNUM:
631
- case T_FILE:
632
- case T_UNDEF:
633
- case T_NODE:
634
- 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:
635
703
  {
636
704
  if (rb_respond_to(value, rb_intern("to_s"))) {
637
705
  // TODO: if this throws we're screwed
638
706
  value = rb_funcall(value, rb_intern("to_s"), 0);
639
707
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
640
708
  }
641
- return scope.Escape(String::NewFromUtf8(isolate, "Undefined Conversion"));
709
+ return scope.Escape(
710
+ String::NewFromUtf8Literal(isolate, "Undefined Conversion"));
642
711
  }
643
712
  }
644
-
645
713
  }
646
714
 
647
715
  static void unblock_eval(void *ptr) {
@@ -649,6 +717,81 @@ static void unblock_eval(void *ptr) {
649
717
  eval->context_info->isolate_info->interrupted = true;
650
718
  }
651
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
+
652
795
  static VALUE rb_snapshot_size(VALUE self, VALUE str) {
653
796
  SnapshotInfo* snapshot_info;
654
797
  Data_Get_Struct(self, SnapshotInfo, snapshot_info);
@@ -667,7 +810,7 @@ static VALUE rb_snapshot_load(VALUE self, VALUE str) {
667
810
 
668
811
  init_v8();
669
812
 
670
- StartupData startup_data = V8::CreateSnapshotDataBlob(RSTRING_PTR(str));
813
+ StartupData startup_data = create_snapshot_data_blob(RSTRING_PTR(str));
671
814
 
672
815
  if (startup_data.data == NULL && startup_data.raw_size == 0) {
673
816
  rb_raise(rb_eSnapshotError, "Could not create snapshot, most likely the source is incorrect");
@@ -698,7 +841,7 @@ static VALUE rb_snapshot_warmup_unsafe(VALUE self, VALUE str) {
698
841
  init_v8();
699
842
 
700
843
  StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
701
- 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));
702
845
 
703
846
  if (warm_startup_data.data == NULL && warm_startup_data.raw_size == 0) {
704
847
  rb_raise(rb_eSnapshotError, "Could not warm up snapshot, most likely the source is incorrect");
@@ -761,6 +904,29 @@ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
761
904
  return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
762
905
  }
763
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
+
764
930
  static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
765
931
  ContextInfo* context_info;
766
932
  Data_Get_Struct(self, ContextInfo, context_info);
@@ -877,8 +1043,8 @@ static VALUE convert_result_to_ruby(VALUE self /* context */,
877
1043
  Local<Value> tmp = Local<Value>::New(isolate, *result.value);
878
1044
 
879
1045
  if (result.json) {
880
- Local<String> rstr = tmp->ToString();
881
- 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"));
882
1048
  ret = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
883
1049
  } else {
884
1050
  ret = convert_v8_to_ruby(isolate, *p_ctx, tmp);
@@ -999,6 +1165,8 @@ gvl_ruby_callback(void* data) {
999
1165
  VALUE result;
1000
1166
  VALUE self;
1001
1167
  VALUE parent;
1168
+ ContextInfo* context_info;
1169
+
1002
1170
  {
1003
1171
  HandleScope scope(args->GetIsolate());
1004
1172
  Local<External> external = Local<External>::Cast(args->Data());
@@ -1011,7 +1179,6 @@ gvl_ruby_callback(void* data) {
1011
1179
  return NULL;
1012
1180
  }
1013
1181
 
1014
- ContextInfo* context_info;
1015
1182
  Data_Get_Struct(parent, ContextInfo, context_info);
1016
1183
 
1017
1184
  if (length > 0) {
@@ -1021,7 +1188,7 @@ gvl_ruby_callback(void* data) {
1021
1188
  for (int i = 0; i < length; i++) {
1022
1189
  Local<Value> value = ((*args)[i]).As<Value>();
1023
1190
  VALUE tmp = convert_v8_to_ruby(args->GetIsolate(),
1024
- *context_info->context, value);
1191
+ *context_info->context, value);
1025
1192
  rb_ary_push(ruby_args, tmp);
1026
1193
  }
1027
1194
  }
@@ -1034,7 +1201,9 @@ gvl_ruby_callback(void* data) {
1034
1201
  callback_data.failed = false;
1035
1202
 
1036
1203
  if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
1037
- 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"));
1038
1207
  args->GetIsolate()->TerminateExecution();
1039
1208
  if (length > 0) {
1040
1209
  rb_ary_clear(ruby_args);
@@ -1048,11 +1217,11 @@ gvl_ruby_callback(void* data) {
1048
1217
 
1049
1218
  if(callback_data.failed) {
1050
1219
  rb_iv_set(parent, "@current_exception", result);
1051
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
1220
+ args->GetIsolate()->ThrowException(String::NewFromUtf8Literal(args->GetIsolate(), "Ruby exception"));
1052
1221
  }
1053
1222
  else {
1054
1223
  HandleScope scope(args->GetIsolate());
1055
- 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);
1056
1225
  args->GetReturnValue().Set(v8_result);
1057
1226
  }
1058
1227
 
@@ -1109,8 +1278,10 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1109
1278
  Local<Context> context = context_info->context->Get(isolate);
1110
1279
  Context::Scope context_scope(context);
1111
1280
 
1112
- Local<String> v8_str = String::NewFromUtf8(isolate, RSTRING_PTR(name),
1113
- 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();
1114
1285
 
1115
1286
  // copy self so we can access from v8 external
1116
1287
  VALUE* self_copy;
@@ -1120,24 +1291,38 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1120
1291
  Local<Value> external = External::New(isolate, self_copy);
1121
1292
 
1122
1293
  if (parent_object == Qnil) {
1123
- context->Global()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
1124
- } else {
1294
+ context->Global()->Set(
1295
+ context,
1296
+ v8_str,
1297
+ FunctionTemplate::New(isolate, ruby_callback, external)
1298
+ ->GetFunction(context)
1299
+ .ToLocalChecked());
1125
1300
 
1126
- Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
1127
- 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();
1128
1307
 
1129
1308
  MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
1130
1309
  if (parsed_script.IsEmpty()) {
1131
- parse_error = true;
1310
+ parse_error = true;
1132
1311
  } else {
1133
- MaybeLocal<Value> maybe_value = parsed_script.ToLocalChecked()->Run(context);
1312
+ MaybeLocal<Value> maybe_value =
1313
+ parsed_script.ToLocalChecked()->Run(context);
1134
1314
  attach_error = true;
1135
1315
 
1136
1316
  if (!maybe_value.IsEmpty()) {
1137
1317
  Local<Value> value = maybe_value.ToLocalChecked();
1138
- if (value->IsObject()){
1139
- value.As<Object>()->Set(v8_str, FunctionTemplate::New(isolate, ruby_callback, external)->GetFunction());
1140
- 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;
1141
1326
  }
1142
1327
  }
1143
1328
  }
@@ -1167,35 +1352,39 @@ static VALUE rb_context_isolate_mutex(VALUE self) {
1167
1352
  return context_info->isolate_info->mutex;
1168
1353
  }
1169
1354
 
1170
- void free_isolate(IsolateInfo* isolate_info) {
1171
-
1172
- if (isolate_info->isolate) {
1173
- Locker lock(isolate_info->isolate);
1174
- }
1175
-
1176
- if (isolate_info->isolate) {
1177
- if (isolate_info->interrupted) {
1178
- 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");
1179
1361
  } else {
1180
-
1181
- if (isolate_info->pid != getpid()) {
1182
- 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
+ );
1183
1372
  } else {
1184
- isolate_info->isolate->Dispose();
1373
+ isolate->Dispose();
1185
1374
  }
1186
1375
  }
1187
- isolate_info->isolate = NULL;
1376
+ isolate = nullptr;
1188
1377
  }
1189
1378
 
1190
- if (isolate_info->startup_data) {
1191
- delete[] isolate_info->startup_data->data;
1192
- delete isolate_info->startup_data;
1379
+ if (startup_data) {
1380
+ delete[] startup_data->data;
1381
+ delete startup_data;
1193
1382
  }
1194
1383
 
1195
- delete isolate_info->allocator;
1384
+ delete allocator;
1196
1385
  }
1197
1386
 
1198
- static void *free_context_raw(void* arg) {
1387
+ static void free_context_raw(void *arg) {
1199
1388
  ContextInfo* context_info = (ContextInfo*)arg;
1200
1389
  IsolateInfo* isolate_info = context_info->isolate_info;
1201
1390
  Persistent<Context>* context = context_info->context;
@@ -1212,6 +1401,20 @@ static void *free_context_raw(void* arg) {
1212
1401
  }
1213
1402
 
1214
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
+
1215
1418
  return NULL;
1216
1419
  }
1217
1420
 
@@ -1226,22 +1429,17 @@ static void free_context(ContextInfo* context_info) {
1226
1429
 
1227
1430
  if (isolate_info && isolate_info->refs() > 1) {
1228
1431
  pthread_t free_context_thread;
1229
- 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)) {
1230
1434
  fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
1231
1435
  }
1232
-
1233
1436
  } else {
1234
1437
  free_context_raw(context_info_copy);
1235
1438
  }
1236
1439
 
1237
- if (context_info->context && isolate_info && isolate_info->isolate) {
1238
1440
  context_info->context = NULL;
1239
- }
1240
-
1241
- if (isolate_info) {
1242
1441
  context_info->isolate_info = NULL;
1243
1442
  }
1244
- }
1245
1443
 
1246
1444
  static void deallocate_isolate(void* data) {
1247
1445
 
@@ -1255,7 +1453,7 @@ static void mark_isolate(void* data) {
1255
1453
  isolate_info->mark();
1256
1454
  }
1257
1455
 
1258
- void deallocate(void* data) {
1456
+ static void deallocate(void* data) {
1259
1457
  ContextInfo* context_info = (ContextInfo*)data;
1260
1458
 
1261
1459
  free_context(context_info);
@@ -1270,22 +1468,22 @@ static void mark_context(void* data) {
1270
1468
  }
1271
1469
  }
1272
1470
 
1273
- void deallocate_external_function(void * data) {
1471
+ static void deallocate_external_function(void * data) {
1274
1472
  xfree(data);
1275
1473
  }
1276
1474
 
1277
- void deallocate_snapshot(void * data) {
1475
+ static void deallocate_snapshot(void * data) {
1278
1476
  SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
1279
1477
  delete[] snapshot_info->data;
1280
1478
  xfree(snapshot_info);
1281
1479
  }
1282
1480
 
1283
- VALUE allocate_external_function(VALUE klass) {
1481
+ static VALUE allocate_external_function(VALUE klass) {
1284
1482
  VALUE* self = ALLOC(VALUE);
1285
1483
  return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
1286
1484
  }
1287
1485
 
1288
- VALUE allocate(VALUE klass) {
1486
+ static VALUE allocate(VALUE klass) {
1289
1487
  ContextInfo* context_info = ALLOC(ContextInfo);
1290
1488
  context_info->isolate_info = NULL;
1291
1489
  context_info->context = NULL;
@@ -1293,7 +1491,7 @@ VALUE allocate(VALUE klass) {
1293
1491
  return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
1294
1492
  }
1295
1493
 
1296
- VALUE allocate_snapshot(VALUE klass) {
1494
+ static VALUE allocate_snapshot(VALUE klass) {
1297
1495
  SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
1298
1496
  snapshot_info->data = NULL;
1299
1497
  snapshot_info->raw_size = 0;
@@ -1301,7 +1499,7 @@ VALUE allocate_snapshot(VALUE klass) {
1301
1499
  return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
1302
1500
  }
1303
1501
 
1304
- VALUE allocate_isolate(VALUE klass) {
1502
+ static VALUE allocate_isolate(VALUE klass) {
1305
1503
  IsolateInfo* isolate_info = new IsolateInfo();
1306
1504
 
1307
1505
  return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
@@ -1340,6 +1538,71 @@ rb_heap_stats(VALUE self) {
1340
1538
  return rval;
1341
1539
  }
1342
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
+
1343
1606
  static VALUE
1344
1607
  rb_context_stop(VALUE self) {
1345
1608
 
@@ -1392,13 +1655,23 @@ nogvl_context_call(void *args) {
1392
1655
  if (!call) {
1393
1656
  return 0;
1394
1657
  }
1395
- Isolate* isolate = call->context_info->isolate_info->isolate;
1658
+ IsolateInfo *isolate_info = call->context_info->isolate_info;
1659
+ Isolate* isolate = isolate_info->isolate;
1396
1660
 
1397
1661
  // in gvl flag
1398
1662
  isolate->SetData(IN_GVL, (void*)false);
1399
1663
  // terminate ASAP
1400
1664
  isolate->SetData(DO_TERMINATE, (void*)false);
1401
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
+
1402
1675
  Isolate::Scope isolate_scope(isolate);
1403
1676
  EscapableHandleScope handle_scope(isolate);
1404
1677
  TryCatch trycatch(isolate);
@@ -1456,6 +1729,13 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1456
1729
  call_argv = argv + 1;
1457
1730
  }
1458
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
+
1459
1739
  bool missingFunction = false;
1460
1740
  {
1461
1741
  Locker lock(isolate);
@@ -1467,8 +1747,11 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1467
1747
 
1468
1748
  // examples of such usage can be found in
1469
1749
  // https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
1470
- Local<String> fname = String::NewFromUtf8(isolate, call.function_name);
1471
- 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
+ }
1472
1755
 
1473
1756
  if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1474
1757
  missingFunction = true;
@@ -1484,7 +1767,7 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1484
1767
  return Qnil;
1485
1768
  }
1486
1769
  for(int i=0; i < fun_argc; i++) {
1487
- call.argv[i] = convert_ruby_to_v8(isolate, call_argv[i]);
1770
+ call.argv[i] = convert_ruby_to_v8(isolate, context, call_argv[i]);
1488
1771
  }
1489
1772
  }
1490
1773
  #if RUBY_API_VERSION_MAJOR > 1
@@ -1517,12 +1800,44 @@ static VALUE rb_context_create_isolate_value(VALUE self) {
1517
1800
  return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
1518
1801
  }
1519
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
+
1520
1824
  extern "C" {
1521
1825
 
1522
- void Init_sq_mini_racer_extension ( void )
1826
+ __attribute__((visibility("default"))) void Init_sq_mini_racer_extension ( void )
1523
1827
  {
1524
- 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
+ }
1525
1839
  VALUE rb_mMiniRacer = rb_define_module_under(rb_mSqreen, "MiniRacer");
1840
+ rb_define_module_function(rb_mMiniRacer, "monotime", (VALUE(*)(...))&rb_monotime, 0);
1526
1841
  rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1527
1842
  rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
1528
1843
  rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
@@ -1548,6 +1863,8 @@ extern "C" {
1548
1863
  rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
1549
1864
  rb_define_method(rb_cContext, "low_memory_notification", (VALUE(*)(...))&rb_context_low_memory_notification, 0);
1550
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
+
1551
1868
  rb_define_private_method(rb_cContext, "create_isolate_value",(VALUE(*)(...))&rb_context_create_isolate_value, 0);
1552
1869
  rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
1553
1870
  rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
@@ -1567,8 +1884,19 @@ extern "C" {
1567
1884
  rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1568
1885
 
1569
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);
1570
1889
  rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
1571
1890
 
1572
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
+ }
1573
1901
  }
1574
1902
  }