sq_mini_racer 0.2.5.0.2 → 0.3.1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 124526376fa9a93ba73fd7205e15abca399022b37b91f5f6fd994ecadaf63714
4
- data.tar.gz: 5a20fb645e0fbf6f8da6b4bdfbda258f29c05e6d9861928a28c2f2b0311bf7f1
3
+ metadata.gz: d8cbb749f96b48a2a73449a6d1cd185b52666e06cafde1403b272546febe73f3
4
+ data.tar.gz: 8f043ac401c8352821c390f8bb77a6f049bef9fd1554bdb999bb176159dedfce
5
5
  SHA512:
6
- metadata.gz: b2cb15116b9cda7b82245dee09f93f02cb4634911ef29d2606d084436b7d0d284b6940d69cbc8fd8dc83bfb865b586c943f0c4e13513b8c66ed2437116aaa340
7
- data.tar.gz: f09c7e9b3ad2c3a34514bca93b5f209e1f49a2d8b873a0edf4f1ec0ba55c16dad035809a71364b7da1a817a8677342ed63cbd6a8bd0c4e588db6ee10b3b035af
6
+ metadata.gz: 12b21a0d7d26dd3a4ff555322126f2a93d6e3ecfd6a510aff13af9b097334b43709ad2b8fdd8453736f157364eda2ad6f7bfc22de4c97294eefa519bc9cffd74
7
+ data.tar.gz: e9f45f0ff41d1939ce62acaeeeae6ecf2aa4d1744c87b6067043bac7480bddb94410a99a27a92526cdfdc006a597cd6d0e55bde992a52bfcfb3c49491d7dc7ce
data/.gitignore CHANGED
@@ -11,3 +11,12 @@ lib/*.so
11
11
  /lib/sq_mini_racer/
12
12
  /ext/mini_racer_extension/mini_racer.creator.*
13
13
  *.bundle
14
+ /mini_racer.config
15
+ /mini_racer.creator
16
+ /mini_racer.creator.user
17
+ /mini_racer.files
18
+ /mini_racer.includes
19
+ /compile_commands.json
20
+ /.vscode/
21
+ /.clangd/
22
+ /.idea/
data/CHANGELOG CHANGED
@@ -1,3 +1,76 @@
1
+ - Unreleased
2
+
3
+ - FIX: on downgrade mkmf was picking the wrong version of libv8, this fix will correct future issues
4
+
5
+ - 23-07-2020
6
+
7
+ - 0.3.1
8
+
9
+ - FIX: specify that libv8 must be larger than 8.4.255 but smaller than 8.5, this avoids issues going forward
10
+
11
+ - 22-07-2020
12
+
13
+ - 0.3.0
14
+
15
+ - FEATURE: upgraded to libv8 version 8.4.255.0
16
+
17
+ - 29-06-2020
18
+
19
+ - 0.2.15
20
+
21
+ - FEATURE: basic wasm support via pump_message_loop
22
+
23
+ - 15-05-2020
24
+
25
+ - 0.2.14
26
+
27
+ - FIX: ensure_gc_after_idle should take in milliseconds like the rest of the APIs not seconds
28
+ - FEATURE: strict params on MiniRacer::Context.new
29
+
30
+ - 15-05-2020
31
+
32
+ - 0.2.13
33
+
34
+ - FIX: edge case around ensure_gc_after_idle possibly firing when context is not idle
35
+
36
+ - 15-05-2020
37
+
38
+ - 0.2.12
39
+
40
+ - FEATURE: isolate.low_memory_notification which can force a full GC
41
+ - FEATURE: MiniRacer::Context.new(ensure_gc_after_idle: 2) - to force full GC 2 seconds after context is idle, this allows you to conserve memory on isolates
42
+
43
+ - 14-05-2020
44
+
45
+ - 0.2.11
46
+
47
+ - FIX: dumping heap snapshots was not flushing the file leading to corrupt snapshots
48
+ - FIX: a use-after-free shutdown crash
49
+
50
+ - 0.2.10
51
+
52
+ - 22-04-2020
53
+
54
+ - FEATURE: memory softlimit support for nogvl_context_call
55
+
56
+ - 0.2.9
57
+
58
+ - 09-01-2020
59
+
60
+ - FIX: correct segfault when JS returns a Symbol and properly cast to ruby symbol
61
+
62
+ - 0.2.8
63
+
64
+ - 11-11-2019
65
+
66
+ - FIX: ensure thread live cycle is properly accounter for following file descriptor fix
67
+
68
+ - 0.2.7
69
+
70
+ - 11-11-2019
71
+
72
+ - FIX: release the file descriptor for timeout pipe earlier (this avoids holding too many files open in Ruby 2.7)
73
+
1
74
  - 14-05-2019
2
75
 
3
76
  - 0.2.6
data/Dockerfile CHANGED
@@ -20,4 +20,3 @@ RUN bundle exec rake compile
20
20
 
21
21
  COPY . /code/
22
22
  CMD bundle exec irb -rmini_racer
23
-
data/README.md CHANGED
@@ -234,12 +234,17 @@ context = MiniRacer::Context.new(isolate: isolate)
234
234
  # give up to 100ms for V8 garbage collection
235
235
  isolate.idle_notification(100)
236
236
 
237
+ # force V8 to perform a full GC
238
+ isolate.low_memory_notification
239
+
237
240
  ```
238
241
 
239
242
  This can come in handy to force V8 GC runs for example in between requests if you use MiniRacer on a web application.
240
243
 
241
244
  Note that this method maps directly to [`v8::Isolate::IdleNotification`](http://bespin.cz/~ondras/html/classv8_1_1Isolate.html#aea16cbb2e351de9a3ae7be2b7cb48297), and that in particular its return value is the same (true if there is no further garbage to collect, false otherwise) and the same caveats apply, in particular that `there is no guarantee that the [call will return] within the time limit.`
242
245
 
246
+ Additionally you may automate this process on a context by defining it with `MiniRacer::Content.new(ensure_gc_after_idle: 1000)`. Using this will ensure V8 will run a full GC using `context.isolate.low_memory_notification` 1 second after the last eval on the context. Low memory notification is both slower and more aggressive than an idle_notification and will ensure long living isolates use minimal amounts of memory.
247
+
243
248
  ### V8 Runtime flags
244
249
 
245
250
  It is possible to set V8 Runtime flags:
@@ -277,7 +282,7 @@ context.eval js
277
282
  The same code without the harmony runtime flag results in a `MiniRacer::RuntimeError: RangeError: Maximum call stack size exceeded` exception.
278
283
  Please refer to http://node.green/ as a reference on other harmony features.
279
284
 
280
- A list of all V8 runtime flags can be found using `node --v8-options`, or else by perusing [the V8 source code for flags (make sure to use the right version of V8)](https://github.com/v8/v8/blob/master/src/flag-definitions.h).
285
+ A list of all V8 runtime flags can be found using `node --v8-options`, or else by perusing [the V8 source code for flags (make sure to use the right version of V8)](https://github.com/v8/v8/blob/master/src/flags/flag-definitions.h).
281
286
 
282
287
  Note that runtime flags must be set before any other operation (e.g. creating a context, a snapshot or an isolate), otherwise an exception will be thrown.
283
288
 
@@ -314,6 +319,16 @@ context.eval("a = 2")
314
319
  # nothing works on the context from now on, its a shell waiting to be disposed
315
320
  ```
316
321
 
322
+ A MiniRacer context can also be dumped in a heapsnapshot file using `#write_heap_snapshot(file_or_io)`
323
+
324
+ ```ruby
325
+ context = MiniRacer::Context.new(timeout: 5)
326
+ context.eval("let a='testing';")
327
+ context.write_heap_snapshot("test.heapsnapshot")
328
+ ```
329
+
330
+ This file can then be loaded in the memory tab of the chrome dev console.
331
+
317
332
  ### Function call
318
333
 
319
334
  This calls the function passed as first argument:
@@ -451,7 +466,7 @@ Add this to your .travis.yml file:
451
466
 
452
467
  ## Contributing
453
468
 
454
- Bug reports and pull requests are welcome on GitHub at https://github.com/discourse/mini_racer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
469
+ Bug reports and pull requests are welcome on GitHub at https://github.com/rubyjs/mini_racer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
455
470
 
456
471
 
457
472
  ## License
data/Rakefile CHANGED
@@ -1,75 +1,57 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
  require "rake/extensiontask"
4
+ require "shellwords"
5
+
6
+ class ValgrindTestTask < Rake::TestTask
7
+ VALGRIND_EXEC = 'valgrind'
8
+ DEFAULT_VALGRIND_OPTS = %w{
9
+ --trace-children=yes
10
+ --partial-loads-ok=yes
11
+ --error-limit=no
12
+ --error-exitcode=33
13
+ --num-callers=100
14
+ --suppressions=valgrind.supp
15
+ --gen-suppressions=all
16
+ }
17
+
18
+ attr_accessor :valgrind_args
19
+
20
+ def initialize(name=:valgrind_test)
21
+ @valgrind_args = DEFAULT_VALGRIND_OPTS
22
+ super
23
+ end
4
24
 
5
- Rake::TestTask.new(:test) do |t|
6
- t.libs << "test"
7
- t.libs << "lib"
8
- t.test_files = FileList['test/**/*_test.rb']
9
- end
10
-
11
- task :default => [:compile, :test]
12
-
13
- gem = Gem::Specification.load( File.dirname(__FILE__) + '/sq_mini_racer.gemspec' )
14
- Rake::ExtensionTask.new( 'mini_racer_extension', gem ) do |ext|
15
- ext.name = 'sq_mini_racer_extension'
16
- end
17
- Rake::ExtensionTask.new('prv_ext_loader', gem)
18
-
19
-
20
- # via http://blog.flavorjon.es/2009/06/easily-valgrind-gdb-your-ruby-c.html
21
- namespace :test do
22
- desc "run test suite with Address Sanitizer"
23
- task :asan do
24
- ENV["CONFIGURE_ARGS"] = [ENV["CONFIGURE_ARGS"], '--enable-asan'].compact.join(' ')
25
- Rake::Task['compile'].invoke
26
-
27
- asan_path = `ldconfig -N -p |grep libasan | grep -v 32 | sed 's/.* => \\(.*\\)$/\\1/'`.chomp.split("\n")[-1]
28
-
29
-
30
- cmdline = "env LD_PRELOAD=\"#{asan_path}\" ruby test/test_leak.rb"
31
- puts cmdline
32
- system cmdline
33
-
34
- cmdline = "env LD_PRELOAD=\"#{asan_path}\" rake test"
35
- puts cmdline
36
- system cmdline
25
+ # see original def in fileutils.rb
26
+ def ruby(*args, &block)
27
+ options = (Hash === args.last) ? args.pop : {}
28
+ if args.length > 1
29
+ sh(*([VALGRIND_EXEC] + valgrind_args + [RUBY] + args + [options]), &block)
30
+ else
31
+ # if the size is 1 it's assumed the arguments are already escaped
32
+ non_escaped_args = [VALGRIND_EXEC] + valgrind_args + [RUBY]
33
+ sh("#{non_escaped_args.map { |s| Shellwords.escape(s) }.join(' ')} #{args.first}", options, &block)
37
34
  end
38
- # partial-loads-ok and undef-value-errors necessary to ignore
39
- # spurious (and eminently ignorable) warnings from the ruby
40
- # interpreter
41
- VALGRIND_BASIC_OPTS = "--num-callers=50 --error-limit=no \
42
- --partial-loads-ok=yes --undef-value-errors=no"
35
+ end
36
+ end
43
37
 
44
- desc "run test suite under valgrind with basic ruby options"
45
- task :valgrind => :compile do
46
- cmdline = "valgrind #{VALGRIND_BASIC_OPTS} ruby test/test_leak.rb"
47
- puts cmdline
48
- system cmdline
38
+ test_task_cfg = Proc.new do |t|
39
+ t.libs << 'test'
40
+ t.libs << 'lib'
41
+ t.test_files = FileList['test/**/*_test.rb']
49
42
  end
50
43
 
51
- desc "run test suite under valgrind with leak-check=full"
52
- task :valgrind_leak_check => :compile do
53
- cmdline = "valgrind #{VALGRIND_BASIC_OPTS} --leak-check=full ruby test/test_leak.rb"
54
- puts cmdline
55
- require 'open3'
56
- _, stderr = Open3.capture3(cmdline)
44
+ Rake::TestTask.new(:test, &test_task_cfg)
45
+ ValgrindTestTask.new(:'test:valgrind', &test_task_cfg)
57
46
 
58
- section = ""
59
- stderr.split("\n").each do |line|
47
+ task :default => [:compile, :test]
60
48
 
61
- if line =~ /==.*==\s*$/
62
- if (section =~ /mini_racer|SUMMARY/)
63
- puts
64
- puts section
65
- puts
66
- end
67
- section = ""
68
- else
69
- section << line << "\n"
70
- end
71
- end
72
- end
49
+ gem = Gem::Specification.load( File.dirname(__FILE__) + '/mini_racer.gemspec' )
50
+ Rake::ExtensionTask.new( 'mini_racer_loader', gem ) do |ext|
51
+ ext.name = 'sq_mini_racer_loader'
52
+ end
53
+ Rake::ExtensionTask.new( 'mini_racer_extension', gem ) do |ext|
54
+ ext.name = 'sq_mini_racer_extension'
73
55
  end
74
56
 
75
57
  desc 'run clang-tidy linter on mini_racer_extension.cc'
data/azure-template.yml CHANGED
@@ -66,7 +66,7 @@ jobs:
66
66
  displayName: Print logs
67
67
  condition: failed()
68
68
 
69
- - script: $(bundle) exec rake test
69
+ - script: TESTOPTS=--junit $(bundle) exec rake test
70
70
  displayName: Run tests
71
71
  env:
72
72
  ${{ if eq(parameters.with_mini_racer, true) }}:
@@ -74,6 +74,15 @@ jobs:
74
74
  ${{ if eq(parameters.with_therubyracer, true) }}:
75
75
  LOAD_THERUBYRACER: 1
76
76
 
77
+ - task: PublishTestResults@2
78
+ displayName: Publish test results
79
+ condition: succeededOrFailed()
80
+ inputs:
81
+ testResultsFormat: 'JUnit'
82
+ testResultsFiles: '**/report.xml'
83
+ searchFolder: '$(System.DefaultWorkingDirectory)'
84
+ mergeTestResults: true
85
+
77
86
  - ${{ if and(eq(parameters.imageName, 'ubuntu-18.04'), eq(parameters.with_mini_racer, false), eq(parameters.with_therubyracer, false)) }}:
78
87
  - script: $(bundle) exec rake test:valgrind
79
88
  displayName: Run tests w/ valgrind
@@ -26,14 +26,11 @@ def cppflags_add_cpu_extension!
26
26
  end
27
27
 
28
28
  def libv8_gem_name
29
- #return "libv8-solaris" if IS_SOLARIS
30
- #return "libv8-alpine" if IS_LINUX_MUSL
31
-
32
29
  'libv8-node'
33
30
  end
34
31
 
35
32
  def libv8_requirement
36
- '~> 12.18.4.0.beta1'
33
+ '~> 15.5.1.0.beta1'
37
34
  end
38
35
 
39
36
  def libv8_basename(version)
@@ -207,19 +204,25 @@ $CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic"
207
204
  $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or IS_DARWIN
208
205
  $CPPFLAGS += " -std=c++0x"
209
206
  $CPPFLAGS += " -fpermissive"
207
+ $CPPFLAGS += " -DV8_COMPRESS_POINTERS"
208
+ $CPPFLAGS += " -fvisibility=hidden "
210
209
  cppflags_add_frame_pointer!
211
210
  cppflags_add_cpu_extension!
212
211
 
213
212
  $CPPFLAGS += " -Wno-reserved-user-defined-literal" if IS_DARWIN
214
213
 
215
214
  $LDFLAGS.insert(0, " -stdlib=libc++ ") if IS_DARWIN
216
- $LDFLAGS += " -Wl,--no-undefined " unless IS_DARWIN
217
- $LDFLAGS += " -Wl,-undefined,error " if IS_DARWIN
215
+ # causes problems on some systems
216
+ # $LDFLAGS += " -Wl,--no-undefined " unless IS_DARWIN
217
+ # $LDFLAGS += " -Wl,-undefined,error " if IS_DARWIN
218
218
 
219
219
  if ENV['CXX']
220
220
  puts "SETTING CXX"
221
221
  CONFIG['CXX'] = ENV['CXX']
222
222
  end
223
+ # 1.9 has no $CXXFLAGS
224
+ $CPPFLAGS += " #{ENV['CPPFLAGS']}" if ENV['CPPFLAGS']
225
+ $LDFLAGS += " #{ENV['LDFLAGS']}" if ENV['LDFLAGS']
223
226
 
224
227
  CXX11_TEST = <<EOS
225
228
  #if __cplusplus <= 199711L
@@ -34,7 +34,11 @@
34
34
  #include <atomic>
35
35
  #include <math.h>
36
36
  #include "compat.hpp"
37
+ #ifdef __x86_64__
37
38
  #include "simdutf8check.h"
39
+ #endif
40
+
41
+ #include <time.h>
38
42
 
39
43
  using namespace v8;
40
44
 
@@ -49,6 +53,7 @@ public:
49
53
  ArrayBuffer::Allocator* allocator;
50
54
  StartupData* startup_data;
51
55
  bool interrupted;
56
+ bool added_gc_cb;
52
57
  pid_t pid;
53
58
  VALUE mutex;
54
59
 
@@ -66,15 +71,12 @@ public:
66
71
 
67
72
 
68
73
  IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
69
- interrupted(false), pid(getpid()), refs_count(0) {
74
+ interrupted(false), added_gc_cb(false), pid(getpid()), refs_count(0) {
70
75
  VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
71
76
  mutex = rb_class_new_instance(0, nullptr, cMutex);
72
77
  }
73
78
 
74
- ~IsolateInfo() {
75
- void free_isolate(IsolateInfo*);
76
- free_isolate(this);
77
- }
79
+ ~IsolateInfo();
78
80
 
79
81
  void init(SnapshotInfo* snapshot_info = nullptr);
80
82
 
@@ -151,6 +153,7 @@ typedef struct {
151
153
  Local<Function> fun;
152
154
  Local<Value> *argv;
153
155
  EvalResult result;
156
+ size_t max_memory;
154
157
  } FunctionCall;
155
158
 
156
159
  enum IsolateFlags {
@@ -179,6 +182,11 @@ static VALUE rb_cDateTime = Qnil;
179
182
  static std::unique_ptr<Platform> current_platform = NULL;
180
183
  static std::mutex platform_lock;
181
184
 
185
+ static pthread_attr_t *thread_attr_p;
186
+ static pthread_rwlock_t exit_lock = PTHREAD_RWLOCK_INITIALIZER;
187
+ static bool ruby_exiting = false; // guarded by exit_lock
188
+ static bool single_threaded = false;
189
+
182
190
  static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
183
191
  bool platform_already_initialized = false;
184
192
 
@@ -190,6 +198,9 @@ static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
190
198
  platform_lock.lock();
191
199
 
192
200
  if (current_platform == NULL) {
201
+ if (!strcmp(RSTRING_PTR(flag_as_str), "--single_threaded")) {
202
+ single_threaded = true;
203
+ }
193
204
  V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
194
205
  } else {
195
206
  platform_already_initialized = true;
@@ -256,11 +267,13 @@ static void prepare_result(MaybeLocal<Value> v8res,
256
267
  Local<Value> local_value = v8res.ToLocalChecked();
257
268
  if ((local_value->IsObject() || local_value->IsArray()) &&
258
269
  !local_value->IsDate() && !local_value->IsFunction()) {
259
- Local<Object> JSON = context->Global()->Get(String::NewFromUtf8(isolate, "JSON"))
260
- ->ToObject(context).ToLocalChecked();
270
+ Local<Object> JSON = context->Global()->Get(
271
+ context, String::NewFromUtf8Literal(isolate, "JSON"))
272
+ .ToLocalChecked().As<Object>();
261
273
 
262
- Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
263
- .As<Function>();
274
+ Local<Function> stringify = JSON->Get(
275
+ context, v8::String::NewFromUtf8Literal(isolate, "stringify"))
276
+ .ToLocalChecked().As<Function>();
264
277
 
265
278
  Local<Object> object = local_value->ToObject(context).ToLocalChecked();
266
279
  const unsigned argc = 1;
@@ -314,7 +327,7 @@ static void prepare_result(MaybeLocal<Value> v8res,
314
327
  } else if(trycatch.HasTerminated()) {
315
328
  evalRes.terminated = true;
316
329
  evalRes.message = new Persistent<Value>();
317
- Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
330
+ Local<String> tmp = String::NewFromUtf8Literal(isolate, "JavaScript was terminated (either by timeout or explicitly)");
318
331
  evalRes.message->Reset(isolate, tmp);
319
332
  }
320
333
  if (!trycatch.StackTrace(context).IsEmpty()) {
@@ -335,7 +348,8 @@ nogvl_context_eval(void* arg) {
335
348
 
336
349
  EvalParams* eval_params = (EvalParams*)arg;
337
350
  EvalResult* result = eval_params->result;
338
- Isolate* isolate = eval_params->context_info->isolate_info->isolate;
351
+ IsolateInfo* isolate_info = eval_params->context_info->isolate_info;
352
+ Isolate* isolate = isolate_info->isolate;
339
353
 
340
354
  Isolate::Scope isolate_scope(isolate);
341
355
  HandleScope handle_scope(isolate);
@@ -379,7 +393,10 @@ nogvl_context_eval(void* arg) {
379
393
  // parsing successful
380
394
  if (eval_params->max_memory > 0) {
381
395
  isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
396
+ if (!isolate_info->added_gc_cb) {
382
397
  isolate->AddGCEpilogueCallback(gc_callback);
398
+ isolate_info->added_gc_cb = true;
399
+ }
383
400
  }
384
401
 
385
402
  maybe_value = parsed_script.ToLocalChecked()->Run(context);
@@ -392,6 +409,12 @@ nogvl_context_eval(void* arg) {
392
409
  return 0;
393
410
  }
394
411
 
412
+ static VALUE new_empty_failed_conv_obj() {
413
+ // TODO isolate code that translates execption to ruby
414
+ // exception so we can properly return it
415
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
416
+ }
417
+
395
418
  // assumes isolate locking is in place
396
419
  static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
397
420
  Local<Value> value) {
@@ -423,8 +446,11 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
423
446
  VALUE rb_array = rb_ary_new();
424
447
  Local<Array> arr = Local<Array>::Cast(value);
425
448
  for(uint32_t i=0; i < arr->Length(); i++) {
426
- Local<Value> element = arr->Get(i);
427
- VALUE rb_elem = convert_v8_to_ruby(isolate, context, element);
449
+ MaybeLocal<Value> element = arr->Get(context, i);
450
+ if (element.IsEmpty()) {
451
+ continue;
452
+ }
453
+ VALUE rb_elem = convert_v8_to_ruby(isolate, context, element.ToLocalChecked());
428
454
  if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
429
455
  return rb_elem;
430
456
  }
@@ -454,26 +480,47 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
454
480
  if (!maybe_props.IsEmpty()) {
455
481
  Local<Array> props = maybe_props.ToLocalChecked();
456
482
  for(uint32_t i=0; i < props->Length(); i++) {
457
- Local<Value> key = props->Get(i);
458
- VALUE rb_key = convert_v8_to_ruby(isolate, context, key);
459
- Local<Value> prop_value = object->Get(key);
460
- // this may have failed due to Get raising
483
+ MaybeLocal<Value> key = props->Get(context, i);
484
+ if (key.IsEmpty()) {
485
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
486
+ }
487
+ VALUE rb_key = convert_v8_to_ruby(isolate, context, key.ToLocalChecked());
461
488
 
462
- if (trycatch.HasCaught()) {
463
- // TODO isolate code that translates execption to ruby
464
- // exception so we can properly return it
465
- return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
489
+ MaybeLocal<Value> prop_value = object->Get(context, key.ToLocalChecked());
490
+ // this may have failed due to Get raising
491
+ if (prop_value.IsEmpty() || trycatch.HasCaught()) {
492
+ return new_empty_failed_conv_obj();
466
493
  }
467
494
 
468
- VALUE rb_value = convert_v8_to_ruby(isolate, context, prop_value);
495
+ VALUE rb_value = convert_v8_to_ruby(
496
+ isolate, context, prop_value.ToLocalChecked());
469
497
  rb_hash_aset(rb_hash, rb_key, rb_value);
470
498
  }
471
499
  }
472
500
  return rb_hash;
473
501
  }
474
502
 
475
- Local<String> rstr = value->ToString(context).ToLocalChecked();
476
- return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
503
+ if (value->IsSymbol()) {
504
+ v8::String::Utf8Value symbol_name(isolate,
505
+ Local<Symbol>::Cast(value)->Name());
506
+
507
+ VALUE str_symbol = rb_enc_str_new(
508
+ *symbol_name,
509
+ symbol_name.length(),
510
+ rb_enc_find("utf-8")
511
+ );
512
+
513
+ return ID2SYM(rb_intern_str(str_symbol));
514
+ }
515
+
516
+ MaybeLocal<String> rstr_maybe = value->ToString(context);
517
+
518
+ if (rstr_maybe.IsEmpty()) {
519
+ return Qnil;
520
+ } else {
521
+ Local<String> rstr = rstr_maybe.ToLocalChecked();
522
+ return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
523
+ }
477
524
  }
478
525
 
479
526
  static VALUE convert_v8_to_ruby(Isolate* isolate,
@@ -519,11 +566,13 @@ static inline Local<Value> convert_ruby_str_to_v8(
519
566
  static const rb_encoding *usascii_enc = rb_usascii_encoding();
520
567
  static const rb_encoding *latin1_enc = rb_enc_find("ISO-8859-1");
521
568
  assert(latin1_enc != nullptr);
569
+ #ifdef __x86_64__
522
570
  #ifndef __AVX2__
523
571
  # define validate_utf8 validate_utf8_fast
524
572
  #else
525
573
  static const (*validate_utf8)(const char *, size_t) =
526
574
  best_utf8_validate_func();
575
+ #endif
527
576
  #endif
528
577
 
529
578
  rb_encoding *enc = rb_enc_get(value);
@@ -532,8 +581,12 @@ static inline Local<Value> convert_ruby_str_to_v8(
532
581
  if (len < 0 || len > INT_MAX) {
533
582
  return Null(isolate);
534
583
  }
584
+ #ifdef __x86_64__
535
585
  bool is_valid_utf8 = enc == utf8_enc &&
536
586
  validate_utf8(str, static_cast<size_t>(len));
587
+ #else
588
+ bool is_valid_utf8 = false;
589
+ #endif
537
590
 
538
591
  MaybeLocal<String> v8str;
539
592
  int int_len = static_cast<int>(len);
@@ -596,7 +649,11 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context,
596
649
  case T_FLOAT:
597
650
  return scope.Escape(Number::New(isolate, NUM2DBL(value)));
598
651
  case T_STRING:
599
- return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
652
+ //#ifndef __x86_64__
653
+ // return scope.Escape(String::NewFromUtf8(isolate, RSTRING_PTR(value), NewStringType::kNormal, (int)RSTRING_LEN(value)).ToLocalChecked());
654
+ //#else
655
+ return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
656
+ //#endif
600
657
  case T_NIL:
601
658
  return scope.Escape(Null(isolate));
602
659
  case T_TRUE:
@@ -608,7 +665,7 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context,
608
665
  length = RARRAY_LEN(value);
609
666
  array = Array::New(isolate, (int)length);
610
667
  for(i=0; i<length; i++) {
611
- array->Set(i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
668
+ array->Set(context, i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
612
669
  }
613
670
  return scope.Escape(array);
614
671
  }
@@ -619,7 +676,7 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context,
619
676
  length = RARRAY_LEN(hash_as_array);
620
677
  for(i=0; i<length; i++) {
621
678
  pair = rb_ary_entry(hash_as_array, i);
622
- object->Set(convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
679
+ object->Set(context, convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
623
680
  convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 1)));
624
681
  }
625
682
  return scope.Escape(object);
@@ -627,7 +684,11 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context,
627
684
  case T_SYMBOL:
628
685
  {
629
686
  value = rb_funcall(value, rb_intern("to_s"), 0);
630
- return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
687
+ //#ifndef __x86_64__
688
+ // return scope.Escape(String::NewFromUtf8(isolate, RSTRING_PTR(value), NewStringType::kNormal, (int)RSTRING_LEN(value)).ToLocalChecked());
689
+ //#else
690
+ return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
691
+ //#endif
631
692
  }
632
693
  case T_DATA:
633
694
  {
@@ -659,10 +720,15 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context,
659
720
  if (rb_respond_to(value, rb_intern("to_s"))) {
660
721
  // TODO: if this throws we're screwed
661
722
  value = rb_funcall(value, rb_intern("to_s"), 0);
723
+ //#ifndef __x86_64__
724
+ // return scope.Escape(String::NewFromUtf8(isolate, RSTRING_PTR(value), NewStringType::kNormal, (int)RSTRING_LEN(value)).ToLocalChecked());
725
+ //#else
662
726
  return scope.Escape(convert_ruby_str_to_v8(scope, isolate, value));
727
+ //#endif
663
728
  }
664
- return scope.Escape(String::NewFromUtf8(isolate, "Undefined Conversion"));
665
- }
729
+ return scope.Escape(
730
+ String::NewFromUtf8Literal(isolate, "Undefined Conversion"));
731
+ }
666
732
  }
667
733
  }
668
734
 
@@ -675,53 +741,43 @@ static void unblock_eval(void *ptr) {
675
741
  * The implementations of the run_extra_code(), create_snapshot_data_blob() and
676
742
  * warm_up_snapshot_data_blob() functions have been derived from V8's test suite.
677
743
  */
678
- bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
744
+ static bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
679
745
  const char *utf8_source, const char *name) {
680
746
  Context::Scope context_scope(context);
681
747
  TryCatch try_catch(isolate);
682
748
  Local<String> source_string;
683
- if (!String::NewFromUtf8(isolate, utf8_source,
684
- NewStringType::kNormal)
685
- .ToLocal(&source_string)) {
749
+ if (!String::NewFromUtf8(isolate, utf8_source).ToLocal(&source_string)) {
686
750
  return false;
687
751
  }
688
- Local<v8::String> resource_name =
689
- String::NewFromUtf8(isolate, name, NewStringType::kNormal)
690
- .ToLocalChecked();
752
+ Local<String> resource_name =
753
+ String::NewFromUtf8(isolate, name).ToLocalChecked();
691
754
  ScriptOrigin origin(resource_name);
692
755
  ScriptCompiler::Source source(source_string, origin);
693
756
  Local<Script> script;
694
757
  if (!ScriptCompiler::Compile(context, &source).ToLocal(&script))
695
758
  return false;
696
- if (script->Run(context).IsEmpty())
697
- return false;
698
- // CHECK(!try_catch.HasCaught());
759
+ if (script->Run(context).IsEmpty()) return false;
699
760
  return true;
700
761
  }
701
762
 
702
- StartupData
763
+ static StartupData
703
764
  create_snapshot_data_blob(const char *embedded_source = nullptr) {
704
- // Create a new isolate and a new context from scratch, optionally run
705
- // a script to embed, and serialize to create a snapshot blob.
706
- StartupData result = {nullptr, 0};
765
+ Isolate *isolate = Isolate::Allocate();
766
+
767
+ // Optionally run a script to embed, and serialize to create a snapshot blob.
768
+ SnapshotCreator snapshot_creator(isolate);
707
769
  {
708
- SnapshotCreator snapshot_creator;
709
- Isolate *isolate = snapshot_creator.GetIsolate();
710
- {
711
770
  HandleScope scope(isolate);
712
- Local<Context> context = Context::New(isolate);
771
+ Local<v8::Context> context = v8::Context::New(isolate);
713
772
  if (embedded_source != nullptr &&
714
- !run_extra_code(isolate, context, embedded_source,
715
- "<embedded>")) {
716
- return result;
773
+ !run_extra_code(isolate, context, embedded_source, "<embedded>")) {
774
+ return {};
717
775
  }
718
776
  snapshot_creator.SetDefaultContext(context);
719
777
  }
720
- result = snapshot_creator.CreateBlob(
778
+ return snapshot_creator.CreateBlob(
721
779
  SnapshotCreator::FunctionCodeHandling::kClear);
722
780
  }
723
- return result;
724
- }
725
781
 
726
782
  StartupData warm_up_snapshot_data_blob(StartupData cold_snapshot_blob,
727
783
  const char *warmup_source) {
@@ -868,6 +924,29 @@ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
868
924
  return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
869
925
  }
870
926
 
927
+ static VALUE rb_isolate_low_memory_notification(VALUE self) {
928
+ IsolateInfo* isolate_info;
929
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
930
+
931
+ if (current_platform == NULL) return Qfalse;
932
+
933
+ isolate_info->isolate->LowMemoryNotification();
934
+ return Qnil;
935
+ }
936
+
937
+ static VALUE rb_isolate_pump_message_loop(VALUE self) {
938
+ IsolateInfo* isolate_info;
939
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
940
+
941
+ if (current_platform == NULL) return Qfalse;
942
+
943
+ if (platform::PumpMessageLoop(current_platform.get(), isolate_info->isolate)){
944
+ return Qtrue;
945
+ } else {
946
+ return Qfalse;
947
+ }
948
+ }
949
+
871
950
  static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
872
951
  ContextInfo* context_info;
873
952
  Data_Get_Struct(self, ContextInfo, context_info);
@@ -1142,7 +1221,9 @@ gvl_ruby_callback(void* data) {
1142
1221
  callback_data.failed = false;
1143
1222
 
1144
1223
  if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
1145
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Terminated execution during transition from Ruby to JS"));
1224
+ args->GetIsolate()->ThrowException(
1225
+ String::NewFromUtf8Literal(args->GetIsolate(),
1226
+ "Terminated execution during transition from Ruby to JS"));
1146
1227
  args->GetIsolate()->TerminateExecution();
1147
1228
  if (length > 0) {
1148
1229
  rb_ary_clear(ruby_args);
@@ -1156,7 +1237,7 @@ gvl_ruby_callback(void* data) {
1156
1237
 
1157
1238
  if(callback_data.failed) {
1158
1239
  rb_iv_set(parent, "@current_exception", result);
1159
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
1240
+ args->GetIsolate()->ThrowException(String::NewFromUtf8Literal(args->GetIsolate(), "Ruby exception"));
1160
1241
  }
1161
1242
  else {
1162
1243
  HandleScope scope(args->GetIsolate());
@@ -1231,7 +1312,9 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1231
1312
 
1232
1313
  if (parent_object == Qnil) {
1233
1314
  context->Global()->Set(
1234
- v8_str, FunctionTemplate::New(isolate, ruby_callback, external)
1315
+ context,
1316
+ v8_str,
1317
+ FunctionTemplate::New(isolate, ruby_callback, external)
1235
1318
  ->GetFunction(context)
1236
1319
  .ToLocalChecked());
1237
1320
 
@@ -1244,7 +1327,7 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1244
1327
 
1245
1328
  MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
1246
1329
  if (parsed_script.IsEmpty()) {
1247
- parse_error = true;
1330
+ parse_error = true;
1248
1331
  } else {
1249
1332
  MaybeLocal<Value> maybe_value =
1250
1333
  parsed_script.ToLocalChecked()->Run(context);
@@ -1254,11 +1337,12 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1254
1337
  Local<Value> value = maybe_value.ToLocalChecked();
1255
1338
  if (value->IsObject()) {
1256
1339
  value.As<Object>()->Set(
1257
- v8_str, FunctionTemplate::New(
1258
- isolate, ruby_callback, external)
1340
+ context,
1341
+ v8_str,
1342
+ FunctionTemplate::New(isolate, ruby_callback, external)
1259
1343
  ->GetFunction(context)
1260
1344
  .ToLocalChecked());
1261
- attach_error = false;
1345
+ attach_error = false;
1262
1346
  }
1263
1347
  }
1264
1348
  }
@@ -1288,35 +1372,39 @@ static VALUE rb_context_isolate_mutex(VALUE self) {
1288
1372
  return context_info->isolate_info->mutex;
1289
1373
  }
1290
1374
 
1291
- void free_isolate(IsolateInfo* isolate_info) {
1292
-
1293
- if (isolate_info->isolate) {
1294
- Locker lock(isolate_info->isolate);
1295
- }
1296
-
1297
- if (isolate_info->isolate) {
1298
- if (isolate_info->interrupted) {
1299
- fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, it can not be disposed and memory will not be reclaimed till the Ruby process exits.\n");
1375
+ IsolateInfo::~IsolateInfo() {
1376
+ if (isolate) {
1377
+ if (this->interrupted) {
1378
+ fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, "
1379
+ "it can not be disposed and memory will not be "
1380
+ "reclaimed till the Ruby process exits.\n");
1300
1381
  } else {
1301
-
1302
- if (isolate_info->pid != getpid()) {
1303
- fprintf(stderr, "WARNING: V8 isolate was forked, it can not be disposed and memory will not be reclaimed till the Ruby process exits.\n");
1382
+ if (this->pid != getpid() && !single_threaded) {
1383
+ fprintf(stderr, "WARNING: V8 isolate was forked, "
1384
+ "it can not be disposed and "
1385
+ "memory will not be reclaimed "
1386
+ "till the Ruby process exits.\n"
1387
+ "It is VERY likely your process will hang.\n"
1388
+ "If you wish to use v8 in forked environment "
1389
+ "please ensure the platform is initialized with:\n"
1390
+ "MiniRacer::Platform.set_flags! :single_threaded\n"
1391
+ );
1304
1392
  } else {
1305
- isolate_info->isolate->Dispose();
1393
+ isolate->Dispose();
1306
1394
  }
1307
1395
  }
1308
- isolate_info->isolate = NULL;
1396
+ isolate = nullptr;
1309
1397
  }
1310
1398
 
1311
- if (isolate_info->startup_data) {
1312
- delete[] isolate_info->startup_data->data;
1313
- delete isolate_info->startup_data;
1399
+ if (startup_data) {
1400
+ delete[] startup_data->data;
1401
+ delete startup_data;
1314
1402
  }
1315
1403
 
1316
- delete isolate_info->allocator;
1404
+ delete allocator;
1317
1405
  }
1318
1406
 
1319
- static void *free_context_raw(void* arg) {
1407
+ static void free_context_raw(void *arg) {
1320
1408
  ContextInfo* context_info = (ContextInfo*)arg;
1321
1409
  IsolateInfo* isolate_info = context_info->isolate_info;
1322
1410
  Persistent<Context>* context = context_info->context;
@@ -1333,6 +1421,20 @@ static void *free_context_raw(void* arg) {
1333
1421
  }
1334
1422
 
1335
1423
  xfree(context_info);
1424
+ }
1425
+
1426
+ static void *free_context_thr(void* arg) {
1427
+ if (pthread_rwlock_tryrdlock(&exit_lock) != 0) {
1428
+ return NULL;
1429
+ }
1430
+ if (ruby_exiting) {
1431
+ return NULL;
1432
+ }
1433
+
1434
+ free_context_raw(arg);
1435
+
1436
+ pthread_rwlock_unlock(&exit_lock);
1437
+
1336
1438
  return NULL;
1337
1439
  }
1338
1440
 
@@ -1347,22 +1449,17 @@ static void free_context(ContextInfo* context_info) {
1347
1449
 
1348
1450
  if (isolate_info && isolate_info->refs() > 1) {
1349
1451
  pthread_t free_context_thread;
1350
- if (pthread_create(&free_context_thread, NULL, free_context_raw, (void*)context_info_copy)) {
1452
+ if (pthread_create(&free_context_thread, thread_attr_p,
1453
+ free_context_thr, (void*)context_info_copy)) {
1351
1454
  fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
1352
1455
  }
1353
-
1354
1456
  } else {
1355
1457
  free_context_raw(context_info_copy);
1356
1458
  }
1357
1459
 
1358
- if (context_info->context && isolate_info && isolate_info->isolate) {
1359
1460
  context_info->context = NULL;
1360
- }
1361
-
1362
- if (isolate_info) {
1363
1461
  context_info->isolate_info = NULL;
1364
1462
  }
1365
- }
1366
1463
 
1367
1464
  static void deallocate_isolate(void* data) {
1368
1465
 
@@ -1376,7 +1473,7 @@ static void mark_isolate(void* data) {
1376
1473
  isolate_info->mark();
1377
1474
  }
1378
1475
 
1379
- void deallocate(void* data) {
1476
+ static void deallocate(void* data) {
1380
1477
  ContextInfo* context_info = (ContextInfo*)data;
1381
1478
 
1382
1479
  free_context(context_info);
@@ -1391,22 +1488,22 @@ static void mark_context(void* data) {
1391
1488
  }
1392
1489
  }
1393
1490
 
1394
- void deallocate_external_function(void * data) {
1491
+ static void deallocate_external_function(void * data) {
1395
1492
  xfree(data);
1396
1493
  }
1397
1494
 
1398
- void deallocate_snapshot(void * data) {
1495
+ static void deallocate_snapshot(void * data) {
1399
1496
  SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
1400
1497
  delete[] snapshot_info->data;
1401
1498
  xfree(snapshot_info);
1402
1499
  }
1403
1500
 
1404
- VALUE allocate_external_function(VALUE klass) {
1501
+ static VALUE allocate_external_function(VALUE klass) {
1405
1502
  VALUE* self = ALLOC(VALUE);
1406
1503
  return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
1407
1504
  }
1408
1505
 
1409
- VALUE allocate(VALUE klass) {
1506
+ static VALUE allocate(VALUE klass) {
1410
1507
  ContextInfo* context_info = ALLOC(ContextInfo);
1411
1508
  context_info->isolate_info = NULL;
1412
1509
  context_info->context = NULL;
@@ -1414,7 +1511,7 @@ VALUE allocate(VALUE klass) {
1414
1511
  return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
1415
1512
  }
1416
1513
 
1417
- VALUE allocate_snapshot(VALUE klass) {
1514
+ static VALUE allocate_snapshot(VALUE klass) {
1418
1515
  SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
1419
1516
  snapshot_info->data = NULL;
1420
1517
  snapshot_info->raw_size = 0;
@@ -1422,7 +1519,7 @@ VALUE allocate_snapshot(VALUE klass) {
1422
1519
  return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
1423
1520
  }
1424
1521
 
1425
- VALUE allocate_isolate(VALUE klass) {
1522
+ static VALUE allocate_isolate(VALUE klass) {
1426
1523
  IsolateInfo* isolate_info = new IsolateInfo();
1427
1524
 
1428
1525
  return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
@@ -1519,6 +1616,8 @@ rb_heap_snapshot(VALUE self, VALUE file) {
1519
1616
  FileOutputStream stream(fp);
1520
1617
  snap->Serialize(&stream, HeapSnapshot::kJSON);
1521
1618
 
1619
+ fflush(fp);
1620
+
1522
1621
  const_cast<HeapSnapshot*>(snap)->Delete();
1523
1622
 
1524
1623
  return Qtrue;
@@ -1576,13 +1675,23 @@ nogvl_context_call(void *args) {
1576
1675
  if (!call) {
1577
1676
  return 0;
1578
1677
  }
1579
- Isolate* isolate = call->context_info->isolate_info->isolate;
1678
+ IsolateInfo *isolate_info = call->context_info->isolate_info;
1679
+ Isolate* isolate = isolate_info->isolate;
1580
1680
 
1581
1681
  // in gvl flag
1582
1682
  isolate->SetData(IN_GVL, (void*)false);
1583
1683
  // terminate ASAP
1584
1684
  isolate->SetData(DO_TERMINATE, (void*)false);
1585
1685
 
1686
+ if (call->max_memory > 0) {
1687
+ isolate->SetData(MEM_SOFTLIMIT_VALUE, &call->max_memory);
1688
+ isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)false);
1689
+ if (!isolate_info->added_gc_cb) {
1690
+ isolate->AddGCEpilogueCallback(gc_callback);
1691
+ isolate_info->added_gc_cb = true;
1692
+ }
1693
+ }
1694
+
1586
1695
  Isolate::Scope isolate_scope(isolate);
1587
1696
  EscapableHandleScope handle_scope(isolate);
1588
1697
  TryCatch trycatch(isolate);
@@ -1640,6 +1749,13 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1640
1749
  call_argv = argv + 1;
1641
1750
  }
1642
1751
 
1752
+ call.max_memory = 0;
1753
+ VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
1754
+ if (mem_softlimit != Qnil) {
1755
+ unsigned long sl_int = NUM2ULONG(mem_softlimit);
1756
+ call.max_memory = (size_t)sl_int;
1757
+ }
1758
+
1643
1759
  bool missingFunction = false;
1644
1760
  {
1645
1761
  Locker lock(isolate);
@@ -1651,8 +1767,11 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1651
1767
 
1652
1768
  // examples of such usage can be found in
1653
1769
  // https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
1654
- Local<String> fname = String::NewFromUtf8(isolate, call.function_name);
1655
- MaybeLocal<v8::Value> val = context->Global()->Get(fname);
1770
+ MaybeLocal<String> fname = String::NewFromUtf8(isolate, call.function_name);
1771
+ MaybeLocal<v8::Value> val;
1772
+ if (!fname.IsEmpty()) {
1773
+ val = context->Global()->Get(context, fname.ToLocalChecked());
1774
+ }
1656
1775
 
1657
1776
  if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1658
1777
  missingFunction = true;
@@ -1701,12 +1820,44 @@ static VALUE rb_context_create_isolate_value(VALUE self) {
1701
1820
  return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
1702
1821
  }
1703
1822
 
1823
+ static void set_ruby_exiting(VALUE value) {
1824
+ (void)value;
1825
+
1826
+ int res = pthread_rwlock_wrlock(&exit_lock);
1827
+
1828
+ ruby_exiting = true;
1829
+ if (res == 0) {
1830
+ pthread_rwlock_unlock(&exit_lock);
1831
+ }
1832
+ }
1833
+
1834
+ static VALUE rb_monotime(VALUE self) {
1835
+ struct timespec ts;
1836
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
1837
+ return INT2FIX(-1);
1838
+ }
1839
+
1840
+ return DBL2NUM(
1841
+ (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0);
1842
+ }
1843
+
1704
1844
  extern "C" {
1705
1845
 
1706
- void Init_sq_mini_racer_extension ( void )
1846
+ __attribute__((visibility("default"))) void Init_sq_mini_racer_extension ( void )
1707
1847
  {
1708
- VALUE rb_mSqreen = rb_define_module("Sqreen");
1848
+ ID sqreen_id = rb_intern("Sqreen");
1849
+ VALUE rb_mSqreen;
1850
+ if (rb_const_defined(rb_cObject, sqreen_id)) {
1851
+ rb_mSqreen = rb_const_get(rb_cObject, sqreen_id);
1852
+ if (TYPE(rb_mSqreen) != T_MODULE) {
1853
+ rb_raise(rb_eTypeError, "Sqreen is not a module");
1854
+ return;
1855
+ }
1856
+ } else {
1857
+ rb_mSqreen = rb_define_module("Sqreen");
1858
+ }
1709
1859
  VALUE rb_mMiniRacer = rb_define_module_under(rb_mSqreen, "MiniRacer");
1860
+ rb_define_module_function(rb_mMiniRacer, "monotime", (VALUE(*)(...))&rb_monotime, 0);
1710
1861
  rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
1711
1862
  rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
1712
1863
  rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
@@ -1753,8 +1904,23 @@ extern "C" {
1753
1904
  rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1754
1905
 
1755
1906
  rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
1907
+ rb_define_method(rb_cIsolate, "low_memory_notification", (VALUE(*)(...))&rb_isolate_low_memory_notification, 0);
1908
+ rb_define_method(rb_cIsolate, "pump_message_loop", (VALUE(*)(...))&rb_isolate_pump_message_loop, 0);
1756
1909
  rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
1757
1910
 
1758
1911
  rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
1912
+
1913
+ rb_set_end_proc(set_ruby_exiting, Qnil);
1914
+
1915
+ static pthread_attr_t attr;
1916
+ if (pthread_attr_init(&attr) == 0) {
1917
+ if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0) {
1918
+ thread_attr_p = &attr;
1919
+ }
1920
+ }
1921
+ auto on_fork_for_child = []() {
1922
+ exit_lock = PTHREAD_RWLOCK_INITIALIZER;
1923
+ };
1924
+ pthread_atfork(nullptr, nullptr, on_fork_for_child);
1759
1925
  }
1760
1926
  }