sq_mini_racer 0.2.5.0.2 → 0.3.1.0.4

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