mini_racer 0.2.8 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f6c48eedae322fc66e7dc1f816d63d8d093a18a02167a16cada1cd04a34e821
4
- data.tar.gz: 0b4de89162986b4d729abff697c04de4f54216bb1a94c5d7737b7cf011fca85e
3
+ metadata.gz: e73ac3193df3f33b51463ba1125ac4eee312caecd47833c545e098ba91d11ece
4
+ data.tar.gz: d6d754829b36953d239ba85e84d2fc88c0af3ccbadd3f8d2e4cd9759bd24fab2
5
5
  SHA512:
6
- metadata.gz: ce21ef957f04319ed5109e6f5ddbc2c17be7f657d9beed5941950ccf57d36054341c5f6685c790ab217d8445e1af1758e7d16c1745d47955042453c6294784c3
7
- data.tar.gz: 4e58fdfde4f8f78ade9c470510296cb8dcd30e3c02a8827f4c4ba68ee49e41dee66de5419938c52c98d9df41d1dd02433b5b0d5389f0d0664da100f5087e0db1
6
+ metadata.gz: 52a0799d84be10bb03774789b3d12d4f0fe2d4159d310b9c6a1ae3dcf878a99cd6b7c786fae813f29754a894ecf5fcc6b7e07834c0aba994a16da5c54d75595b
7
+ data.tar.gz: bc478bdb582d7b786d28754200063d7ef185396e0da23840f006679bc1af6707f54c7a719918360afa9b24c2b4ab083ca081e2b2d6853095ecd5949a9482ecbd
data/.dockerignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ lib/mini_racer_extension.so
11
+ lib/mini_racer_loader.so
12
+ *.bundle
@@ -0,0 +1,80 @@
1
+ name: Test
2
+ on:
3
+ - push
4
+
5
+ jobs:
6
+ test-darwin:
7
+ strategy:
8
+ fail-fast: false
9
+ matrix:
10
+ os:
11
+ - '10.15'
12
+ - '11.0'
13
+ platform:
14
+ - x86_64
15
+ # arm64
16
+ name: Test (darwin)
17
+ runs-on: macos-${{ matrix.os }}
18
+ steps:
19
+ - name: Checkout
20
+ uses: actions/checkout@v2
21
+ - name: Bundle
22
+ run: bundle install
23
+ - name: Compile
24
+ run: bundle exec rake compile
25
+ - name: Test
26
+ run: bundle exec rake test
27
+ test-linux:
28
+ strategy:
29
+ fail-fast: false
30
+ matrix:
31
+ ruby:
32
+ - '2.4'
33
+ - '2.5'
34
+ - '2.6'
35
+ - '2.7'
36
+ - '3.0'
37
+ platform:
38
+ - amd64
39
+ - arm64
40
+ # arm
41
+ # ppc64le
42
+ # s390x
43
+ libc:
44
+ - gnu
45
+ - musl
46
+ name: Test (linux)
47
+ runs-on: ubuntu-20.04
48
+ steps:
49
+ - name: Enable ${{ matrix.platform }} platform
50
+ id: qemu
51
+ if: ${{ matrix.platform != 'amd64' }}
52
+ run: |
53
+ docker run --privileged --rm tonistiigi/binfmt:latest --install ${{ matrix.platform }} | tee platforms.json
54
+ echo "::set-output name=platforms::$(cat platforms.json)"
55
+ - name: Start container
56
+ id: container
57
+ run: |
58
+ case ${{ matrix.libc }} in
59
+ gnu)
60
+ echo 'ruby:${{ matrix.ruby }}'
61
+ ;;
62
+ musl)
63
+ echo 'ruby:${{ matrix.ruby }}-alpine'
64
+ ;;
65
+ esac > container_image
66
+ echo "::set-output name=image::$(cat container_image)"
67
+ docker run --rm -d -v "${PWD}":"${PWD}" -w "${PWD}" --platform linux/${{ matrix.platform }} $(cat container_image) /bin/sleep 64d | tee container_id
68
+ docker exec -w "${PWD}" $(cat container_id) uname -a
69
+ echo "::set-output name=id::$(cat container_id)"
70
+ - name: Install Alpine system dependencies
71
+ if: ${{ matrix.libc == 'musl' }}
72
+ run: docker exec -w "${PWD}" ${{ steps.container.outputs.id }} apk add --no-cache build-base linux-headers bash python2 python3 git curl tar clang binutils-gold
73
+ - name: Checkout
74
+ uses: actions/checkout@v2
75
+ - name: Bundle
76
+ run: docker exec -w "${PWD}" ${{ steps.container.outputs.id }} bundle install
77
+ - name: Compile
78
+ run: docker exec -w "${PWD}" ${{ steps.container.outputs.id }} bundle exec rake compile
79
+ - name: Test
80
+ run: docker exec -w "${PWD}" ${{ steps.container.outputs.id }} bundle exec rake test
data/.gitignore CHANGED
@@ -8,4 +8,5 @@ Gemfile.lock
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  lib/mini_racer_extension.so
11
+ lib/mini_racer_loader.so
11
12
  *.bundle
data/.travis.yml CHANGED
@@ -1,23 +1,30 @@
1
1
  language: ruby
2
+ os: linux
2
3
  rvm:
3
- - 2.3
4
4
  - 2.4
5
5
  - 2.5
6
6
  - 2.6
7
+ - 2.7
8
+ - 3.0
7
9
  - ruby-head
8
- matrix:
10
+ arch:
11
+ - amd64
12
+ - arm64
13
+ jobs:
9
14
  include:
10
- - rvm: 2.5.1
15
+ - rvm: 2.5
11
16
  os: osx
12
17
  osx_image: xcode9.4
13
- - rvm: 2.4.0
18
+ - rvm: 2.6
14
19
  os: osx
15
- osx_image: xcode8.3
16
- - rvm: 2.4.0
20
+ osx_image: xcode11.3
21
+ - rvm: 2.6
17
22
  os: osx
18
- osx_image: xcode7.3
19
- dist: trusty
20
- sudo: true
23
+ osx_image: xcode12.2
24
+ - rvm: 2.7
25
+ os: osx
26
+ osx_image: xcode12.2
27
+ dist: xenial
21
28
  before_install:
22
29
  - gem update --system
23
30
  - gem install bundler -v 1.16.2
data/CHANGELOG CHANGED
@@ -1,3 +1,74 @@
1
+ - 11-04-2021
2
+
3
+ - 0.4.0
4
+
5
+ - FEATURE: upgrade to libv8 node 15.14.0 (v8 8.6.395.17)
6
+ - Promote 0.4.0.beta1 to release, using libv8-node release path
7
+
8
+ - 08-04-2021
9
+
10
+ - 0.4.0.beta1
11
+
12
+ - FIX: on downgrade mkmf was picking the wrong version of libv8, this fix will correct future issues
13
+ - FEATURE: upgraded libv8 to use node libv8 build which supports M1 and various ARM builds v8 moved to (8.6.395.17)
14
+
15
+ - 23-07-2020
16
+
17
+ - 0.3.1
18
+
19
+ - FIX: specify that libv8 must be larger than 8.4.255 but smaller than 8.5, this avoids issues going forward
20
+
21
+ - 22-07-2020
22
+
23
+ - 0.3.0
24
+
25
+ - FEATURE: upgraded to libv8 version 8.4.255.0
26
+
27
+ - 29-06-2020
28
+
29
+ - 0.2.15
30
+
31
+ - FEATURE: basic wasm support via pump_message_loop
32
+
33
+ - 15-05-2020
34
+
35
+ - 0.2.14
36
+
37
+ - FIX: ensure_gc_after_idle should take in milliseconds like the rest of the APIs not seconds
38
+ - FEATURE: strict params on MiniRacer::Context.new
39
+
40
+ - 15-05-2020
41
+
42
+ - 0.2.13
43
+
44
+ - FIX: edge case around ensure_gc_after_idle possibly firing when context is not idle
45
+
46
+ - 15-05-2020
47
+
48
+ - 0.2.12
49
+
50
+ - FEATURE: isolate.low_memory_notification which can force a full GC
51
+ - 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
52
+
53
+ - 14-05-2020
54
+
55
+ - 0.2.11
56
+
57
+ - FIX: dumping heap snapshots was not flushing the file leading to corrupt snapshots
58
+ - FIX: a use-after-free shutdown crash
59
+
60
+ - 0.2.10
61
+
62
+ - 22-04-2020
63
+
64
+ - FEATURE: memory softlimit support for nogvl_context_call
65
+
66
+ - 0.2.9
67
+
68
+ - 09-01-2020
69
+
70
+ - FIX: correct segfault when JS returns a Symbol and properly cast to ruby symbol
71
+
1
72
  - 0.2.8
2
73
 
3
74
  - 11-11-2019
data/Dockerfile ADDED
@@ -0,0 +1,21 @@
1
+ ARG RUBY_VERSION=2.7
2
+ FROM ruby:${RUBY_VERSION}
3
+
4
+ RUN test ! -f /etc/alpine-release || apk add --no-cache build-base git
5
+
6
+ # without this `COPY .git`, we get the following error:
7
+ # fatal: not a git repository (or any of the parent directories): .git
8
+ # but with it we need the full gem just to compile the extension because
9
+ # of gemspec's `git --ls-files`
10
+ # COPY .git /code/.git
11
+ COPY Gemfile mini_racer.gemspec /code/
12
+ COPY lib/mini_racer/version.rb /code/lib/mini_racer/version.rb
13
+ WORKDIR /code
14
+ RUN bundle install
15
+
16
+ COPY Rakefile /code/
17
+ COPY ext /code/ext/
18
+ RUN bundle exec rake compile
19
+
20
+ COPY . /code/
21
+ CMD bundle exec irb -rmini_racer
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MiniRacer
2
2
 
3
- [![Build Status](https://travis-ci.org/discourse/mini_racer.svg?branch=master)](https://travis-ci.org/discourse/mini_racer)
3
+ [![Build Status](https://travis-ci.org/rubyjs/mini_racer.svg?branch=master)](https://travis-ci.org/rubyjs/mini_racer)
4
4
 
5
5
  Minimal, modern embedded V8 for Ruby.
6
6
 
@@ -230,12 +230,17 @@ context = MiniRacer::Context.new(isolate: isolate)
230
230
  # give up to 100ms for V8 garbage collection
231
231
  isolate.idle_notification(100)
232
232
 
233
+ # force V8 to perform a full GC
234
+ isolate.low_memory_notification
235
+
233
236
  ```
234
237
 
235
238
  This can come in handy to force V8 GC runs for example in between requests if you use MiniRacer on a web application.
236
239
 
237
240
  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.`
238
241
 
242
+ 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.
243
+
239
244
  ### V8 Runtime flags
240
245
 
241
246
  It is possible to set V8 Runtime flags:
@@ -244,7 +249,7 @@ It is possible to set V8 Runtime flags:
244
249
  MiniRacer::Platform.set_flags! :noconcurrent_recompilation, max_inlining_levels: 10
245
250
  ```
246
251
 
247
- This can come in handy if you want to use MiniRacer with Unicorn, which doesn't seem to alwatys appreciate V8's liberal use of threading:
252
+ This can come in handy if you want to use MiniRacer with Unicorn, which doesn't seem to always appreciate V8's liberal use of threading:
248
253
  ```ruby
249
254
  MiniRacer::Platform.set_flags! :noconcurrent_recompilation, :noconcurrent_sweeping
250
255
  ```
@@ -273,7 +278,7 @@ context.eval js
273
278
  The same code without the harmony runtime flag results in a `MiniRacer::RuntimeError: RangeError: Maximum call stack size exceeded` exception.
274
279
  Please refer to http://node.green/ as a reference on other harmony features.
275
280
 
276
- 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).
281
+ 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).
277
282
 
278
283
  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.
279
284
 
@@ -310,6 +315,16 @@ context.eval("a = 2")
310
315
  # nothing works on the context from now on, its a shell waiting to be disposed
311
316
  ```
312
317
 
318
+ A MiniRacer context can also be dumped in a heapsnapshot file using `#write_heap_snapshot(file_or_io)`
319
+
320
+ ```ruby
321
+ context = MiniRacer::Context.new(timeout: 5)
322
+ context.eval("let a='testing';")
323
+ context.write_heap_snapshot("test.heapsnapshot")
324
+ ```
325
+
326
+ This file can then be loaded in the memory tab of the chrome dev console.
327
+
313
328
  ### Function call
314
329
 
315
330
  This calls the function passed as first argument:
@@ -447,7 +462,7 @@ Add this to your .travis.yml file:
447
462
 
448
463
  ## Contributing
449
464
 
450
- 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.
465
+ 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.
451
466
 
452
467
 
453
468
  ## License
data/Rakefile CHANGED
@@ -11,6 +11,7 @@ end
11
11
  task :default => [:compile, :test]
12
12
 
13
13
  gem = Gem::Specification.load( File.dirname(__FILE__) + '/mini_racer.gemspec' )
14
+ Rake::ExtensionTask.new( 'mini_racer_loader', gem )
14
15
  Rake::ExtensionTask.new( 'mini_racer_extension', gem )
15
16
 
16
17
 
@@ -1,5 +1,7 @@
1
1
  require 'mkmf'
2
- require 'libv8'
2
+ require_relative '../../lib/mini_racer/version'
3
+ gem 'libv8-node', MiniRacer::LIBV8_NODE_VERSION
4
+ require 'libv8-node'
3
5
 
4
6
  IS_DARWIN = RUBY_PLATFORM =~ /darwin/
5
7
 
@@ -11,11 +13,17 @@ $CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic"
11
13
  $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or IS_DARWIN
12
14
  $CPPFLAGS += " -std=c++0x"
13
15
  $CPPFLAGS += " -fpermissive"
16
+ $CPPFLAGS += " -DV8_COMPRESS_POINTERS"
17
+ $CPPFLAGS += " -fvisibility=hidden "
14
18
 
15
19
  $CPPFLAGS += " -Wno-reserved-user-defined-literal" if IS_DARWIN
16
20
 
17
21
  $LDFLAGS.insert(0, " -stdlib=libc++ ") if IS_DARWIN
18
22
 
23
+ # check for missing symbols at link time
24
+ # $LDFLAGS += " -Wl,--no-undefined " unless IS_DARWIN
25
+ # $LDFLAGS += " -Wl,-undefined,error " if IS_DARWIN
26
+
19
27
  if ENV['CXX']
20
28
  puts "SETTING CXX"
21
29
  CONFIG['CXX'] = ENV['CXX']
@@ -53,7 +61,7 @@ if enable_config('debug') || enable_config('asan')
53
61
  CONFIG['debugflags'] << ' -ggdb3 -O0'
54
62
  end
55
63
 
56
- Libv8.configure_makefile
64
+ Libv8::Node.configure_makefile
57
65
 
58
66
  if enable_config('asan')
59
67
  $CPPFLAGS.insert(0, " -fsanitize=address ")
@@ -25,6 +25,7 @@ public:
25
25
  ArrayBuffer::Allocator* allocator;
26
26
  StartupData* startup_data;
27
27
  bool interrupted;
28
+ bool added_gc_cb;
28
29
  pid_t pid;
29
30
  VALUE mutex;
30
31
 
@@ -42,15 +43,12 @@ public:
42
43
 
43
44
 
44
45
  IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
45
- interrupted(false), pid(getpid()), refs_count(0) {
46
+ interrupted(false), added_gc_cb(false), pid(getpid()), refs_count(0) {
46
47
  VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
47
48
  mutex = rb_class_new_instance(0, nullptr, cMutex);
48
49
  }
49
50
 
50
- ~IsolateInfo() {
51
- void free_isolate(IsolateInfo*);
52
- free_isolate(this);
53
- }
51
+ ~IsolateInfo();
54
52
 
55
53
  void init(SnapshotInfo* snapshot_info = nullptr);
56
54
 
@@ -127,6 +125,7 @@ typedef struct {
127
125
  Local<Function> fun;
128
126
  Local<Value> *argv;
129
127
  EvalResult result;
128
+ size_t max_memory;
130
129
  } FunctionCall;
131
130
 
132
131
  enum IsolateFlags {
@@ -155,6 +154,11 @@ static VALUE rb_cDateTime = Qnil;
155
154
  static std::unique_ptr<Platform> current_platform = NULL;
156
155
  static std::mutex platform_lock;
157
156
 
157
+ static pthread_attr_t *thread_attr_p;
158
+ static pthread_rwlock_t exit_lock = PTHREAD_RWLOCK_INITIALIZER;
159
+ static bool ruby_exiting = false; // guarded by exit_lock
160
+ static bool single_threaded = false;
161
+
158
162
  static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
159
163
  bool platform_already_initialized = false;
160
164
 
@@ -166,6 +170,9 @@ static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
166
170
  platform_lock.lock();
167
171
 
168
172
  if (current_platform == NULL) {
173
+ if (!strcmp(RSTRING_PTR(flag_as_str), "--single_threaded")) {
174
+ single_threaded = true;
175
+ }
169
176
  V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
170
177
  } else {
171
178
  platform_already_initialized = true;
@@ -232,11 +239,13 @@ static void prepare_result(MaybeLocal<Value> v8res,
232
239
  Local<Value> local_value = v8res.ToLocalChecked();
233
240
  if ((local_value->IsObject() || local_value->IsArray()) &&
234
241
  !local_value->IsDate() && !local_value->IsFunction()) {
235
- Local<Object> JSON = context->Global()->Get(String::NewFromUtf8(isolate, "JSON"))
236
- ->ToObject(context).ToLocalChecked();
242
+ Local<Object> JSON = context->Global()->Get(
243
+ context, String::NewFromUtf8Literal(isolate, "JSON"))
244
+ .ToLocalChecked().As<Object>();
237
245
 
238
- Local<Function> stringify = JSON->Get(v8::String::NewFromUtf8(isolate, "stringify"))
239
- .As<Function>();
246
+ Local<Function> stringify = JSON->Get(
247
+ context, v8::String::NewFromUtf8Literal(isolate, "stringify"))
248
+ .ToLocalChecked().As<Function>();
240
249
 
241
250
  Local<Object> object = local_value->ToObject(context).ToLocalChecked();
242
251
  const unsigned argc = 1;
@@ -290,7 +299,7 @@ static void prepare_result(MaybeLocal<Value> v8res,
290
299
  } else if(trycatch.HasTerminated()) {
291
300
  evalRes.terminated = true;
292
301
  evalRes.message = new Persistent<Value>();
293
- Local<String> tmp = String::NewFromUtf8(isolate, "JavaScript was terminated (either by timeout or explicitly)");
302
+ Local<String> tmp = String::NewFromUtf8Literal(isolate, "JavaScript was terminated (either by timeout or explicitly)");
294
303
  evalRes.message->Reset(isolate, tmp);
295
304
  }
296
305
  if (!trycatch.StackTrace(context).IsEmpty()) {
@@ -307,7 +316,8 @@ nogvl_context_eval(void* arg) {
307
316
 
308
317
  EvalParams* eval_params = (EvalParams*)arg;
309
318
  EvalResult* result = eval_params->result;
310
- Isolate* isolate = eval_params->context_info->isolate_info->isolate;
319
+ IsolateInfo* isolate_info = eval_params->context_info->isolate_info;
320
+ Isolate* isolate = isolate_info->isolate;
311
321
 
312
322
  Isolate::Scope isolate_scope(isolate);
313
323
  HandleScope handle_scope(isolate);
@@ -351,7 +361,10 @@ nogvl_context_eval(void* arg) {
351
361
  // parsing successful
352
362
  if (eval_params->max_memory > 0) {
353
363
  isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
364
+ if (!isolate_info->added_gc_cb) {
354
365
  isolate->AddGCEpilogueCallback(gc_callback);
366
+ isolate_info->added_gc_cb = true;
367
+ }
355
368
  }
356
369
 
357
370
  maybe_value = parsed_script.ToLocalChecked()->Run(context);
@@ -364,6 +377,12 @@ nogvl_context_eval(void* arg) {
364
377
  return NULL;
365
378
  }
366
379
 
380
+ static VALUE new_empty_failed_conv_obj() {
381
+ // TODO isolate code that translates execption to ruby
382
+ // exception so we can properly return it
383
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
384
+ }
385
+
367
386
  // assumes isolate locking is in place
368
387
  static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
369
388
  Local<Value> value) {
@@ -395,8 +414,11 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
395
414
  VALUE rb_array = rb_ary_new();
396
415
  Local<Array> arr = Local<Array>::Cast(value);
397
416
  for(uint32_t i=0; i < arr->Length(); i++) {
398
- Local<Value> element = arr->Get(i);
399
- VALUE rb_elem = convert_v8_to_ruby(isolate, context, element);
417
+ MaybeLocal<Value> element = arr->Get(context, i);
418
+ if (element.IsEmpty()) {
419
+ continue;
420
+ }
421
+ VALUE rb_elem = convert_v8_to_ruby(isolate, context, element.ToLocalChecked());
400
422
  if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
401
423
  return rb_elem;
402
424
  }
@@ -426,26 +448,47 @@ static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
426
448
  if (!maybe_props.IsEmpty()) {
427
449
  Local<Array> props = maybe_props.ToLocalChecked();
428
450
  for(uint32_t i=0; i < props->Length(); i++) {
429
- Local<Value> key = props->Get(i);
430
- VALUE rb_key = convert_v8_to_ruby(isolate, context, key);
431
- Local<Value> prop_value = object->Get(key);
432
- // this may have failed due to Get raising
451
+ MaybeLocal<Value> key = props->Get(context, i);
452
+ if (key.IsEmpty()) {
453
+ return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
454
+ }
455
+ VALUE rb_key = convert_v8_to_ruby(isolate, context, key.ToLocalChecked());
433
456
 
434
- if (trycatch.HasCaught()) {
435
- // TODO isolate code that translates execption to ruby
436
- // exception so we can properly return it
437
- return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
457
+ MaybeLocal<Value> prop_value = object->Get(context, key.ToLocalChecked());
458
+ // this may have failed due to Get raising
459
+ if (prop_value.IsEmpty() || trycatch.HasCaught()) {
460
+ return new_empty_failed_conv_obj();
438
461
  }
439
462
 
440
- VALUE rb_value = convert_v8_to_ruby(isolate, context, prop_value);
463
+ VALUE rb_value = convert_v8_to_ruby(
464
+ isolate, context, prop_value.ToLocalChecked());
441
465
  rb_hash_aset(rb_hash, rb_key, rb_value);
442
466
  }
443
467
  }
444
468
  return rb_hash;
445
469
  }
446
470
 
447
- Local<String> rstr = value->ToString(context).ToLocalChecked();
448
- return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
471
+ if (value->IsSymbol()) {
472
+ v8::String::Utf8Value symbol_name(isolate,
473
+ Local<Symbol>::Cast(value)->Name());
474
+
475
+ VALUE str_symbol = rb_enc_str_new(
476
+ *symbol_name,
477
+ symbol_name.length(),
478
+ rb_enc_find("utf-8")
479
+ );
480
+
481
+ return ID2SYM(rb_intern_str(str_symbol));
482
+ }
483
+
484
+ MaybeLocal<String> rstr_maybe = value->ToString(context);
485
+
486
+ if (rstr_maybe.IsEmpty()) {
487
+ return Qnil;
488
+ } else {
489
+ Local<String> rstr = rstr_maybe.ToLocalChecked();
490
+ return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
491
+ }
449
492
  }
450
493
 
451
494
  static VALUE convert_v8_to_ruby(Isolate* isolate,
@@ -500,7 +543,7 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context,
500
543
  length = RARRAY_LEN(value);
501
544
  array = Array::New(isolate, (int)length);
502
545
  for(i=0; i<length; i++) {
503
- array->Set(i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
546
+ array->Set(context, i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
504
547
  }
505
548
  return scope.Escape(array);
506
549
  case T_HASH:
@@ -509,7 +552,7 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context,
509
552
  length = RARRAY_LEN(hash_as_array);
510
553
  for(i=0; i<length; i++) {
511
554
  pair = rb_ary_entry(hash_as_array, i);
512
- object->Set(convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
555
+ object->Set(context, convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
513
556
  convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 1)));
514
557
  }
515
558
  return scope.Escape(object);
@@ -539,9 +582,9 @@ static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context,
539
582
  case T_UNDEF:
540
583
  case T_NODE:
541
584
  default:
542
- return scope.Escape(String::NewFromUtf8(isolate, "Undefined Conversion"));
585
+ return scope.Escape(String::NewFromUtf8Literal(isolate, "Undefined Conversion"));
586
+ }
543
587
  }
544
- }
545
588
 
546
589
  static void unblock_eval(void *ptr) {
547
590
  EvalParams* eval = (EvalParams*)ptr;
@@ -552,53 +595,43 @@ static void unblock_eval(void *ptr) {
552
595
  * The implementations of the run_extra_code(), create_snapshot_data_blob() and
553
596
  * warm_up_snapshot_data_blob() functions have been derived from V8's test suite.
554
597
  */
555
- bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
598
+ static bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
556
599
  const char *utf8_source, const char *name) {
557
600
  Context::Scope context_scope(context);
558
601
  TryCatch try_catch(isolate);
559
602
  Local<String> source_string;
560
- if (!String::NewFromUtf8(isolate, utf8_source,
561
- NewStringType::kNormal)
562
- .ToLocal(&source_string)) {
603
+ if (!String::NewFromUtf8(isolate, utf8_source).ToLocal(&source_string)) {
563
604
  return false;
564
605
  }
565
- Local<v8::String> resource_name =
566
- String::NewFromUtf8(isolate, name, NewStringType::kNormal)
567
- .ToLocalChecked();
606
+ Local<String> resource_name =
607
+ String::NewFromUtf8(isolate, name).ToLocalChecked();
568
608
  ScriptOrigin origin(resource_name);
569
609
  ScriptCompiler::Source source(source_string, origin);
570
610
  Local<Script> script;
571
611
  if (!ScriptCompiler::Compile(context, &source).ToLocal(&script))
572
612
  return false;
573
- if (script->Run(context).IsEmpty())
574
- return false;
575
- // CHECK(!try_catch.HasCaught());
613
+ if (script->Run(context).IsEmpty()) return false;
576
614
  return true;
577
615
  }
578
616
 
579
- StartupData
617
+ static StartupData
580
618
  create_snapshot_data_blob(const char *embedded_source = nullptr) {
581
- // Create a new isolate and a new context from scratch, optionally run
582
- // a script to embed, and serialize to create a snapshot blob.
583
- StartupData result = {nullptr, 0};
584
- {
585
- SnapshotCreator snapshot_creator;
586
- Isolate *isolate = snapshot_creator.GetIsolate();
619
+ Isolate *isolate = Isolate::Allocate();
620
+
621
+ // Optionally run a script to embed, and serialize to create a snapshot blob.
622
+ SnapshotCreator snapshot_creator(isolate);
587
623
  {
588
624
  HandleScope scope(isolate);
589
- Local<Context> context = Context::New(isolate);
625
+ Local<v8::Context> context = v8::Context::New(isolate);
590
626
  if (embedded_source != nullptr &&
591
- !run_extra_code(isolate, context, embedded_source,
592
- "<embedded>")) {
593
- return result;
627
+ !run_extra_code(isolate, context, embedded_source, "<embedded>")) {
628
+ return {};
594
629
  }
595
630
  snapshot_creator.SetDefaultContext(context);
596
631
  }
597
- result = snapshot_creator.CreateBlob(
632
+ return snapshot_creator.CreateBlob(
598
633
  SnapshotCreator::FunctionCodeHandling::kClear);
599
634
  }
600
- return result;
601
- }
602
635
 
603
636
  StartupData warm_up_snapshot_data_blob(StartupData cold_snapshot_blob,
604
637
  const char *warmup_source) {
@@ -745,6 +778,29 @@ static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
745
778
  return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
746
779
  }
747
780
 
781
+ static VALUE rb_isolate_low_memory_notification(VALUE self) {
782
+ IsolateInfo* isolate_info;
783
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
784
+
785
+ if (current_platform == NULL) return Qfalse;
786
+
787
+ isolate_info->isolate->LowMemoryNotification();
788
+ return Qnil;
789
+ }
790
+
791
+ static VALUE rb_isolate_pump_message_loop(VALUE self) {
792
+ IsolateInfo* isolate_info;
793
+ Data_Get_Struct(self, IsolateInfo, isolate_info);
794
+
795
+ if (current_platform == NULL) return Qfalse;
796
+
797
+ if (platform::PumpMessageLoop(current_platform.get(), isolate_info->isolate)){
798
+ return Qtrue;
799
+ } else {
800
+ return Qfalse;
801
+ }
802
+ }
803
+
748
804
  static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
749
805
  ContextInfo* context_info;
750
806
  Data_Get_Struct(self, ContextInfo, context_info);
@@ -1015,7 +1071,9 @@ gvl_ruby_callback(void* data) {
1015
1071
  callback_data.failed = false;
1016
1072
 
1017
1073
  if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
1018
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Terminated execution during transition from Ruby to JS"));
1074
+ args->GetIsolate()->ThrowException(
1075
+ String::NewFromUtf8Literal(args->GetIsolate(),
1076
+ "Terminated execution during transition from Ruby to JS"));
1019
1077
  args->GetIsolate()->TerminateExecution();
1020
1078
  if (length > 0) {
1021
1079
  rb_ary_clear(ruby_args);
@@ -1029,7 +1087,7 @@ gvl_ruby_callback(void* data) {
1029
1087
 
1030
1088
  if(callback_data.failed) {
1031
1089
  rb_iv_set(parent, "@current_exception", result);
1032
- args->GetIsolate()->ThrowException(String::NewFromUtf8(args->GetIsolate(), "Ruby exception"));
1090
+ args->GetIsolate()->ThrowException(String::NewFromUtf8Literal(args->GetIsolate(), "Ruby exception"));
1033
1091
  }
1034
1092
  else {
1035
1093
  HandleScope scope(args->GetIsolate());
@@ -1100,7 +1158,9 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1100
1158
 
1101
1159
  if (parent_object == Qnil) {
1102
1160
  context->Global()->Set(
1103
- v8_str, FunctionTemplate::New(isolate, ruby_callback, external)
1161
+ context,
1162
+ v8_str,
1163
+ FunctionTemplate::New(isolate, ruby_callback, external)
1104
1164
  ->GetFunction(context)
1105
1165
  .ToLocalChecked());
1106
1166
 
@@ -1113,7 +1173,7 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1113
1173
 
1114
1174
  MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
1115
1175
  if (parsed_script.IsEmpty()) {
1116
- parse_error = true;
1176
+ parse_error = true;
1117
1177
  } else {
1118
1178
  MaybeLocal<Value> maybe_value =
1119
1179
  parsed_script.ToLocalChecked()->Run(context);
@@ -1123,11 +1183,12 @@ static VALUE rb_external_function_notify_v8(VALUE self) {
1123
1183
  Local<Value> value = maybe_value.ToLocalChecked();
1124
1184
  if (value->IsObject()) {
1125
1185
  value.As<Object>()->Set(
1126
- v8_str, FunctionTemplate::New(
1127
- isolate, ruby_callback, external)
1186
+ context,
1187
+ v8_str,
1188
+ FunctionTemplate::New(isolate, ruby_callback, external)
1128
1189
  ->GetFunction(context)
1129
1190
  .ToLocalChecked());
1130
- attach_error = false;
1191
+ attach_error = false;
1131
1192
  }
1132
1193
  }
1133
1194
  }
@@ -1157,35 +1218,39 @@ static VALUE rb_context_isolate_mutex(VALUE self) {
1157
1218
  return context_info->isolate_info->mutex;
1158
1219
  }
1159
1220
 
1160
- void free_isolate(IsolateInfo* isolate_info) {
1161
-
1162
- if (isolate_info->isolate) {
1163
- Locker lock(isolate_info->isolate);
1164
- }
1165
-
1166
- if (isolate_info->isolate) {
1167
- if (isolate_info->interrupted) {
1168
- 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");
1221
+ IsolateInfo::~IsolateInfo() {
1222
+ if (isolate) {
1223
+ if (this->interrupted) {
1224
+ fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, "
1225
+ "it can not be disposed and memory will not be "
1226
+ "reclaimed till the Ruby process exits.\n");
1169
1227
  } else {
1170
-
1171
- if (isolate_info->pid != getpid()) {
1172
- fprintf(stderr, "WARNING: V8 isolate was forked, it can not be disposed and memory will not be reclaimed till the Ruby process exits.\n");
1228
+ if (this->pid != getpid() && !single_threaded) {
1229
+ fprintf(stderr, "WARNING: V8 isolate was forked, "
1230
+ "it can not be disposed and "
1231
+ "memory will not be reclaimed "
1232
+ "till the Ruby process exits.\n"
1233
+ "It is VERY likely your process will hang.\n"
1234
+ "If you wish to use v8 in forked environment "
1235
+ "please ensure the platform is initialized with:\n"
1236
+ "MiniRacer::Platform.set_flags! :single_threaded\n"
1237
+ );
1173
1238
  } else {
1174
- isolate_info->isolate->Dispose();
1239
+ isolate->Dispose();
1175
1240
  }
1176
1241
  }
1177
- isolate_info->isolate = NULL;
1242
+ isolate = nullptr;
1178
1243
  }
1179
1244
 
1180
- if (isolate_info->startup_data) {
1181
- delete[] isolate_info->startup_data->data;
1182
- delete isolate_info->startup_data;
1245
+ if (startup_data) {
1246
+ delete[] startup_data->data;
1247
+ delete startup_data;
1183
1248
  }
1184
1249
 
1185
- delete isolate_info->allocator;
1250
+ delete allocator;
1186
1251
  }
1187
1252
 
1188
- static void *free_context_raw(void* arg) {
1253
+ static void free_context_raw(void *arg) {
1189
1254
  ContextInfo* context_info = (ContextInfo*)arg;
1190
1255
  IsolateInfo* isolate_info = context_info->isolate_info;
1191
1256
  Persistent<Context>* context = context_info->context;
@@ -1202,6 +1267,20 @@ static void *free_context_raw(void* arg) {
1202
1267
  }
1203
1268
 
1204
1269
  xfree(context_info);
1270
+ }
1271
+
1272
+ static void *free_context_thr(void* arg) {
1273
+ if (pthread_rwlock_tryrdlock(&exit_lock) != 0) {
1274
+ return NULL;
1275
+ }
1276
+ if (ruby_exiting) {
1277
+ return NULL;
1278
+ }
1279
+
1280
+ free_context_raw(arg);
1281
+
1282
+ pthread_rwlock_unlock(&exit_lock);
1283
+
1205
1284
  return NULL;
1206
1285
  }
1207
1286
 
@@ -1215,22 +1294,17 @@ static void free_context(ContextInfo* context_info) {
1215
1294
  context_info_copy->context = context_info->context;
1216
1295
 
1217
1296
  if (isolate_info && isolate_info->refs() > 1) {
1218
- pthread_t free_context_thread;
1219
- if (pthread_create(&free_context_thread, NULL, free_context_raw, (void*)context_info_copy)) {
1220
- fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
1221
- }
1222
-
1297
+ pthread_t free_context_thread;
1298
+ if (pthread_create(&free_context_thread, thread_attr_p,
1299
+ free_context_thr, (void*)context_info_copy)) {
1300
+ fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
1301
+ }
1223
1302
  } else {
1224
1303
  free_context_raw(context_info_copy);
1225
1304
  }
1226
1305
 
1227
- if (context_info->context && isolate_info && isolate_info->isolate) {
1228
- context_info->context = NULL;
1229
- }
1230
-
1231
- if (isolate_info) {
1232
- context_info->isolate_info = NULL;
1233
- }
1306
+ context_info->context = NULL;
1307
+ context_info->isolate_info = NULL;
1234
1308
  }
1235
1309
 
1236
1310
  static void deallocate_isolate(void* data) {
@@ -1245,7 +1319,7 @@ static void mark_isolate(void* data) {
1245
1319
  isolate_info->mark();
1246
1320
  }
1247
1321
 
1248
- void deallocate(void* data) {
1322
+ static void deallocate(void* data) {
1249
1323
  ContextInfo* context_info = (ContextInfo*)data;
1250
1324
 
1251
1325
  free_context(context_info);
@@ -1260,22 +1334,22 @@ static void mark_context(void* data) {
1260
1334
  }
1261
1335
  }
1262
1336
 
1263
- void deallocate_external_function(void * data) {
1337
+ static void deallocate_external_function(void * data) {
1264
1338
  xfree(data);
1265
1339
  }
1266
1340
 
1267
- void deallocate_snapshot(void * data) {
1341
+ static void deallocate_snapshot(void * data) {
1268
1342
  SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
1269
1343
  delete[] snapshot_info->data;
1270
1344
  xfree(snapshot_info);
1271
1345
  }
1272
1346
 
1273
- VALUE allocate_external_function(VALUE klass) {
1347
+ static VALUE allocate_external_function(VALUE klass) {
1274
1348
  VALUE* self = ALLOC(VALUE);
1275
1349
  return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
1276
1350
  }
1277
1351
 
1278
- VALUE allocate(VALUE klass) {
1352
+ static VALUE allocate(VALUE klass) {
1279
1353
  ContextInfo* context_info = ALLOC(ContextInfo);
1280
1354
  context_info->isolate_info = NULL;
1281
1355
  context_info->context = NULL;
@@ -1283,7 +1357,7 @@ VALUE allocate(VALUE klass) {
1283
1357
  return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
1284
1358
  }
1285
1359
 
1286
- VALUE allocate_snapshot(VALUE klass) {
1360
+ static VALUE allocate_snapshot(VALUE klass) {
1287
1361
  SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
1288
1362
  snapshot_info->data = NULL;
1289
1363
  snapshot_info->raw_size = 0;
@@ -1291,7 +1365,7 @@ VALUE allocate_snapshot(VALUE klass) {
1291
1365
  return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
1292
1366
  }
1293
1367
 
1294
- VALUE allocate_isolate(VALUE klass) {
1368
+ static VALUE allocate_isolate(VALUE klass) {
1295
1369
  IsolateInfo* isolate_info = new IsolateInfo();
1296
1370
 
1297
1371
  return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
@@ -1388,6 +1462,8 @@ rb_heap_snapshot(VALUE self, VALUE file) {
1388
1462
  FileOutputStream stream(fp);
1389
1463
  snap->Serialize(&stream, HeapSnapshot::kJSON);
1390
1464
 
1465
+ fflush(fp);
1466
+
1391
1467
  const_cast<HeapSnapshot*>(snap)->Delete();
1392
1468
 
1393
1469
  return Qtrue;
@@ -1428,13 +1504,23 @@ nogvl_context_call(void *args) {
1428
1504
  if (!call) {
1429
1505
  return NULL;
1430
1506
  }
1431
- Isolate* isolate = call->context_info->isolate_info->isolate;
1507
+ IsolateInfo *isolate_info = call->context_info->isolate_info;
1508
+ Isolate* isolate = isolate_info->isolate;
1432
1509
 
1433
1510
  // in gvl flag
1434
1511
  isolate->SetData(IN_GVL, (void*)false);
1435
1512
  // terminate ASAP
1436
1513
  isolate->SetData(DO_TERMINATE, (void*)false);
1437
1514
 
1515
+ if (call->max_memory > 0) {
1516
+ isolate->SetData(MEM_SOFTLIMIT_VALUE, &call->max_memory);
1517
+ isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)false);
1518
+ if (!isolate_info->added_gc_cb) {
1519
+ isolate->AddGCEpilogueCallback(gc_callback);
1520
+ isolate_info->added_gc_cb = true;
1521
+ }
1522
+ }
1523
+
1438
1524
  Isolate::Scope isolate_scope(isolate);
1439
1525
  EscapableHandleScope handle_scope(isolate);
1440
1526
  TryCatch trycatch(isolate);
@@ -1492,6 +1578,13 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1492
1578
  call_argv = argv + 1;
1493
1579
  }
1494
1580
 
1581
+ call.max_memory = 0;
1582
+ VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
1583
+ if (mem_softlimit != Qnil) {
1584
+ unsigned long sl_int = NUM2ULONG(mem_softlimit);
1585
+ call.max_memory = (size_t)sl_int;
1586
+ }
1587
+
1495
1588
  bool missingFunction = false;
1496
1589
  {
1497
1590
  Locker lock(isolate);
@@ -1503,8 +1596,11 @@ static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
1503
1596
 
1504
1597
  // examples of such usage can be found in
1505
1598
  // https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
1506
- Local<String> fname = String::NewFromUtf8(isolate, call.function_name);
1507
- MaybeLocal<v8::Value> val = context->Global()->Get(fname);
1599
+ MaybeLocal<String> fname = String::NewFromUtf8(isolate, call.function_name);
1600
+ MaybeLocal<v8::Value> val;
1601
+ if (!fname.IsEmpty()) {
1602
+ val = context->Global()->Get(context, fname.ToLocalChecked());
1603
+ }
1508
1604
 
1509
1605
  if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
1510
1606
  missingFunction = true;
@@ -1549,9 +1645,20 @@ static VALUE rb_context_create_isolate_value(VALUE self) {
1549
1645
  return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
1550
1646
  }
1551
1647
 
1648
+ static void set_ruby_exiting(VALUE value) {
1649
+ (void)value;
1650
+
1651
+ int res = pthread_rwlock_wrlock(&exit_lock);
1652
+
1653
+ ruby_exiting = true;
1654
+ if (res == 0) {
1655
+ pthread_rwlock_unlock(&exit_lock);
1656
+ }
1657
+ }
1658
+
1552
1659
  extern "C" {
1553
1660
 
1554
- void Init_mini_racer_extension ( void )
1661
+ __attribute__((visibility("default"))) void Init_mini_racer_extension ( void )
1555
1662
  {
1556
1663
  VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
1557
1664
  rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
@@ -1599,8 +1706,23 @@ extern "C" {
1599
1706
  rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
1600
1707
 
1601
1708
  rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
1709
+ rb_define_method(rb_cIsolate, "low_memory_notification", (VALUE(*)(...))&rb_isolate_low_memory_notification, 0);
1710
+ rb_define_method(rb_cIsolate, "pump_message_loop", (VALUE(*)(...))&rb_isolate_pump_message_loop, 0);
1602
1711
  rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
1603
1712
 
1604
1713
  rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
1714
+
1715
+ rb_set_end_proc(set_ruby_exiting, Qnil);
1716
+
1717
+ static pthread_attr_t attr;
1718
+ if (pthread_attr_init(&attr) == 0) {
1719
+ if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0) {
1720
+ thread_attr_p = &attr;
1721
+ }
1722
+ }
1723
+ auto on_fork_for_child = []() {
1724
+ exit_lock = PTHREAD_RWLOCK_INITIALIZER;
1725
+ };
1726
+ pthread_atfork(nullptr, nullptr, on_fork_for_child);
1605
1727
  }
1606
1728
  }