mini_racer 0.2.4 → 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.
@@ -0,0 +1,8 @@
1
+ require 'mkmf'
2
+
3
+ extension_name = 'mini_racer_loader'
4
+ dir_config extension_name
5
+
6
+ $CPPFLAGS += " -fvisibility=hidden "
7
+
8
+ create_makefile extension_name
@@ -0,0 +1,123 @@
1
+ #include <ruby.h>
2
+ #include <dlfcn.h>
3
+ #include <string.h>
4
+ #include <stdint.h>
5
+ #include <stdlib.h>
6
+
7
+ // Load a Ruby extension like Ruby does, only with flags that:
8
+ // a) hide symbols from other extensions (RTLD_LOCAL)
9
+ // b) bind symbols tightly (RTLD_DEEPBIND, when available)
10
+
11
+ void Init_mini_racer_loader(void);
12
+
13
+ static void *_dln_load(const char *file);
14
+
15
+ static VALUE _load_shared_lib(VALUE self, volatile VALUE fname)
16
+ {
17
+ (void) self;
18
+
19
+ // check that path is not tainted
20
+ SafeStringValue(fname);
21
+
22
+ FilePathValue(fname);
23
+ VALUE path = rb_str_encode_ospath(fname);
24
+
25
+ char *loc = StringValueCStr(path);
26
+ void *handle = _dln_load(loc);
27
+
28
+ return handle ? Qtrue : Qfalse;
29
+ }
30
+
31
+ // adapted from Ruby's dln.c
32
+ #define INIT_FUNC_PREFIX ((char[]) {'I', 'n', 'i', 't', '_'})
33
+ #define INIT_FUNCNAME(buf, file) do { \
34
+ const char *base = (file); \
35
+ const size_t flen = _init_funcname(&base); \
36
+ const size_t plen = sizeof(INIT_FUNC_PREFIX); \
37
+ char *const tmp = ALLOCA_N(char, plen + flen + 1); \
38
+ memcpy(tmp, INIT_FUNC_PREFIX, plen); \
39
+ memcpy(tmp+plen, base, flen); \
40
+ tmp[plen+flen] = '\0'; \
41
+ *(buf) = tmp; \
42
+ } while(0)
43
+
44
+ // adapted from Ruby's dln.c
45
+ static size_t _init_funcname(const char **file)
46
+ {
47
+ const char *p = *file,
48
+ *base,
49
+ *dot = NULL;
50
+
51
+ for (base = p; *p; p++) { /* Find position of last '/' */
52
+ if (*p == '.' && !dot) {
53
+ dot = p;
54
+ }
55
+ if (*p == '/') {
56
+ base = p + 1;
57
+ dot = NULL;
58
+ }
59
+ }
60
+ *file = base;
61
+ return (uintptr_t) ((dot ? dot : p) - base);
62
+ }
63
+
64
+ // adapted from Ruby's dln.c
65
+ static void *_dln_load(const char *file)
66
+ {
67
+ char *buf;
68
+ const char *error;
69
+ #define DLN_ERROR() (error = dlerror(), strcpy(ALLOCA_N(char, strlen(error) + 1), error))
70
+
71
+ void *handle;
72
+ void (*init_fct)(void);
73
+
74
+ INIT_FUNCNAME(&buf, file);
75
+
76
+ #ifndef RTLD_DEEPBIND
77
+ # define RTLD_DEEPBIND 0
78
+ #endif
79
+ /* Load file */
80
+ if ((handle = dlopen(file, RTLD_LAZY|RTLD_LOCAL|RTLD_DEEPBIND)) == NULL) {
81
+ DLN_ERROR();
82
+ goto failed;
83
+ }
84
+ #if defined(RUBY_EXPORT)
85
+ {
86
+ static const char incompatible[] = "incompatible library version";
87
+ void *ex = dlsym(handle, "ruby_xmalloc");
88
+ if (ex && ex != (void *) &ruby_xmalloc) {
89
+
90
+ # if defined __APPLE__
91
+ /* dlclose() segfaults */
92
+ rb_fatal("%s - %s", incompatible, file);
93
+ # else
94
+ dlclose(handle);
95
+ error = incompatible;
96
+ goto failed;
97
+ #endif
98
+ }
99
+ }
100
+ # endif
101
+
102
+ init_fct = (void (*)(void)) dlsym(handle, buf);
103
+ if (init_fct == NULL) {
104
+ error = DLN_ERROR();
105
+ dlclose(handle);
106
+ goto failed;
107
+ }
108
+
109
+ /* Call the init code */
110
+ (*init_fct)();
111
+
112
+ return handle;
113
+
114
+ failed:
115
+ rb_raise(rb_eLoadError, "%s", error);
116
+ }
117
+
118
+ __attribute__((visibility("default"))) void Init_mini_racer_loader()
119
+ {
120
+ VALUE mMiniRacer = rb_define_module("MiniRacer");
121
+ VALUE mLoader = rb_define_module_under(mMiniRacer, "Loader");
122
+ rb_define_singleton_method(mLoader, "load", _load_shared_lib, 1);
123
+ }
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiniRacer
2
- VERSION = "0.2.4"
4
+ VERSION = "0.4.0"
5
+ LIBV8_NODE_VERSION = "~> 15.14.0.0"
3
6
  end
data/lib/mini_racer.rb CHANGED
@@ -1,5 +1,15 @@
1
1
  require "mini_racer/version"
2
- require "mini_racer_extension"
2
+ require "mini_racer_loader"
3
+ require "pathname"
4
+
5
+ ext_filename = "mini_racer_extension.#{RbConfig::CONFIG['DLEXT']}"
6
+ ext_path = Gem.loaded_specs['mini_racer'].require_paths
7
+ .map { |p| (p = Pathname.new(p)).absolute? ? p : Pathname.new(__dir__).parent + p }
8
+ ext_found = ext_path.map { |p| p + ext_filename }.find { |p| p.file? }
9
+
10
+ raise LoadError, "Could not find #{ext_filename} in #{ext_path.map(&:to_s)}" unless ext_found
11
+ MiniRacer::Loader.load(ext_found.to_s)
12
+
3
13
  require "thread"
4
14
  require "json"
5
15
 
@@ -130,21 +140,29 @@ module MiniRacer
130
140
  end
131
141
  end
132
142
 
133
- def initialize(options = nil)
143
+ def initialize(max_memory: nil, timeout: nil, isolate: nil, ensure_gc_after_idle: nil, snapshot: nil)
134
144
  options ||= {}
135
145
 
136
- check_init_options!(options)
146
+ check_init_options!(isolate: isolate, snapshot: snapshot, max_memory: max_memory, ensure_gc_after_idle: ensure_gc_after_idle, timeout: timeout)
137
147
 
138
148
  @functions = {}
139
149
  @timeout = nil
140
150
  @max_memory = nil
141
151
  @current_exception = nil
142
- @timeout = options[:timeout]
143
- if options[:max_memory].is_a?(Numeric) && options[:max_memory] > 0
144
- @max_memory = options[:max_memory]
145
- end
152
+ @timeout = timeout
153
+ @max_memory = max_memory
154
+
146
155
  # false signals it should be fetched if requested
147
- @isolate = options[:isolate] || false
156
+ @isolate = isolate || false
157
+
158
+ @ensure_gc_after_idle = ensure_gc_after_idle
159
+
160
+ if @ensure_gc_after_idle
161
+ @last_eval = nil
162
+ @ensure_gc_thread = nil
163
+ @ensure_gc_mutex = Mutex.new
164
+ end
165
+
148
166
  @disposed = false
149
167
 
150
168
  @callback_mutex = Mutex.new
@@ -153,7 +171,7 @@ module MiniRacer
153
171
  @eval_thread = nil
154
172
 
155
173
  # defined in the C class
156
- init_unsafe(options[:isolate], options[:snapshot])
174
+ init_unsafe(isolate, snapshot)
157
175
  end
158
176
 
159
177
  def isolate
@@ -167,6 +185,28 @@ module MiniRacer
167
185
  eval(File.read(filename))
168
186
  end
169
187
 
188
+ def write_heap_snapshot(file_or_io)
189
+ f = nil
190
+ implicit = false
191
+
192
+
193
+ if String === file_or_io
194
+ f = File.open(file_or_io, "w")
195
+ implicit = true
196
+ else
197
+ f = file_or_io
198
+ end
199
+
200
+ if !(File === f)
201
+ raise ArgumentError("file_or_io")
202
+ end
203
+
204
+ write_heap_snapshot_unsafe(f)
205
+
206
+ ensure
207
+ f.close if implicit
208
+ end
209
+
170
210
  def eval(str, options=nil)
171
211
  raise(ContextDisposedError, 'attempted to call eval on a disposed context!') if @disposed
172
212
 
@@ -181,6 +221,7 @@ module MiniRacer
181
221
  end
182
222
  ensure
183
223
  @eval_thread = nil
224
+ ensure_gc_thread if @ensure_gc_after_idle
184
225
  end
185
226
 
186
227
  def call(function_name, *arguments)
@@ -194,15 +235,17 @@ module MiniRacer
194
235
  end
195
236
  ensure
196
237
  @eval_thread = nil
238
+ ensure_gc_thread if @ensure_gc_after_idle
197
239
  end
198
240
 
199
241
  def dispose
200
242
  return if @disposed
201
243
  isolate_mutex.synchronize do
244
+ return if @disposed
202
245
  dispose_unsafe
246
+ @disposed = true
247
+ @isolate = nil # allow it to be garbage collected, if set
203
248
  end
204
- @disposed = true
205
- @isolate = nil # allow it to be garbage collected, if set
206
249
  end
207
250
 
208
251
 
@@ -251,6 +294,38 @@ module MiniRacer
251
294
 
252
295
  private
253
296
 
297
+ def ensure_gc_thread
298
+ @last_eval = Process.clock_gettime(Process::CLOCK_MONOTONIC)
299
+ @ensure_gc_mutex.synchronize do
300
+ @ensure_gc_thread = nil if !@ensure_gc_thread&.alive?
301
+ @ensure_gc_thread ||= Thread.new do
302
+ ensure_gc_after_idle_seconds = @ensure_gc_after_idle / 1000.0
303
+ done = false
304
+ while !done
305
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
306
+
307
+ if @disposed
308
+ @ensure_gc_thread = nil
309
+ break
310
+ end
311
+
312
+ if !@eval_thread && ensure_gc_after_idle_seconds < now - @last_eval
313
+ @ensure_gc_mutex.synchronize do
314
+ isolate_mutex.synchronize do
315
+ if !@eval_thread
316
+ isolate.low_memory_notification if !@disposed
317
+ @ensure_gc_thread = nil
318
+ done = true
319
+ end
320
+ end
321
+ end
322
+ end
323
+ sleep ensure_gc_after_idle_seconds if !done
324
+ end
325
+ end
326
+ end
327
+ end
328
+
254
329
  def stop_attached
255
330
  @callback_mutex.synchronize{
256
331
  if @callback_running
@@ -291,20 +366,42 @@ module MiniRacer
291
366
 
292
367
  # ensure we do not leak a thread in state
293
368
  t.join
369
+ t = nil
294
370
 
295
371
  rval
296
-
372
+ ensure
373
+ # exceptions need to be handled
374
+ if t && wp
375
+ wp.write("done")
376
+ t.join
377
+ end
378
+ wp.close if wp
379
+ rp.close if rp
297
380
  end
298
381
 
299
- def check_init_options!(options)
300
- assert_option_is_nil_or_a('isolate', options[:isolate], Isolate)
301
- assert_option_is_nil_or_a('snapshot', options[:snapshot], Snapshot)
382
+ def check_init_options!(isolate:, snapshot:, max_memory:, ensure_gc_after_idle:, timeout:)
383
+ assert_option_is_nil_or_a('isolate', isolate, Isolate)
384
+ assert_option_is_nil_or_a('snapshot', snapshot, Snapshot)
302
385
 
303
- if options[:isolate] && options[:snapshot]
386
+ assert_numeric_or_nil('max_memory', max_memory, min_value: 10_000)
387
+ assert_numeric_or_nil('ensure_gc_after_idle', ensure_gc_after_idle, min_value: 1)
388
+ assert_numeric_or_nil('timeout', timeout, min_value: 1)
389
+
390
+ if isolate && snapshot
304
391
  raise ArgumentError, 'can only pass one of isolate and snapshot options'
305
392
  end
306
393
  end
307
394
 
395
+ def assert_numeric_or_nil(option_name, object, min_value:)
396
+ if object.is_a?(Numeric) && object < min_value
397
+ raise ArgumentError, "#{option_name} must be larger than #{min_value}"
398
+ end
399
+
400
+ if !object.nil? && !object.is_a?(Numeric)
401
+ raise ArgumentError, "#{option_name} must be a number, passed a #{object.inspect}"
402
+ end
403
+ end
404
+
308
405
  def assert_option_is_nil_or_a(option_name, object, klass)
309
406
  unless object.nil? || object.is_a?(klass)
310
407
  raise ArgumentError, "#{option_name} must be a #{klass} object, passed a #{object.inspect}"
data/mini_racer.gemspec CHANGED
@@ -14,21 +14,28 @@ Gem::Specification.new do |spec|
14
14
  spec.homepage = "https://github.com/discourse/mini_racer"
15
15
  spec.license = "MIT"
16
16
 
17
-
18
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(benchmark|test|spec|features)/}) }
17
+ spec.metadata = {
18
+ "bug_tracker_uri" => "https://github.com/discourse/mini_racer/issues",
19
+ "changelog_uri" => "https://github.com/discourse/mini_racer/blob/v#{spec.version}/CHANGELOG",
20
+ "documentation_uri" => "https://www.rubydoc.info/gems/mini_racer/#{spec.version}",
21
+ "source_code_uri" => "https://github.com/discourse/mini_racer/tree/v#{spec.version}",
22
+ }
23
+
24
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(benchmark|test|spec|features|examples)/}) }
19
25
  spec.bindir = "exe"
20
26
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
27
  spec.require_paths = ["lib"]
22
28
 
23
- spec.add_development_dependency "bundler", "~> 1.12"
24
- spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "bundler"
30
+ spec.add_development_dependency "rake", ">= 12.3.3"
25
31
  spec.add_development_dependency "minitest", "~> 5.0"
26
32
  spec.add_development_dependency "rake-compiler"
33
+ spec.add_development_dependency "m"
27
34
 
28
- spec.add_dependency 'libv8', '>= 6.3'
35
+ spec.add_dependency 'libv8-node', MiniRacer::LIBV8_NODE_VERSION
29
36
  spec.require_paths = ["lib", "ext"]
30
37
 
31
- spec.extensions = ["ext/mini_racer_extension/extconf.rb"]
38
+ spec.extensions = ["ext/mini_racer_loader/extconf.rb", "ext/mini_racer_extension/extconf.rb"]
32
39
 
33
40
  spec.required_ruby_version = '>= 2.3'
34
41
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_racer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-11-02 00:00:00.000000000 Z
11
+ date: 2021-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.12'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.12'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: 12.3.3
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: 12.3.3
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -67,31 +67,49 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: libv8
70
+ name: m
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '6.3'
76
- type: :runtime
75
+ version: '0'
76
+ type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: '6.3'
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: libv8-node
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 15.14.0.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 15.14.0.0
83
97
  description: Minimal embedded v8 engine for Ruby
84
98
  email:
85
99
  - sam.saffron@gmail.com
86
100
  executables: []
87
101
  extensions:
102
+ - ext/mini_racer_loader/extconf.rb
88
103
  - ext/mini_racer_extension/extconf.rb
89
104
  extra_rdoc_files: []
90
105
  files:
106
+ - ".dockerignore"
107
+ - ".github/workflows/ci.yml"
91
108
  - ".gitignore"
92
109
  - ".travis.yml"
93
110
  - CHANGELOG
94
111
  - CODE_OF_CONDUCT.md
112
+ - Dockerfile
95
113
  - Gemfile
96
114
  - LICENSE.txt
97
115
  - README.md
@@ -100,14 +118,20 @@ files:
100
118
  - bin/setup
101
119
  - ext/mini_racer_extension/extconf.rb
102
120
  - ext/mini_racer_extension/mini_racer_extension.cc
121
+ - ext/mini_racer_loader/extconf.rb
122
+ - ext/mini_racer_loader/mini_racer_loader.c
103
123
  - lib/mini_racer.rb
104
124
  - lib/mini_racer/version.rb
105
125
  - mini_racer.gemspec
106
126
  homepage: https://github.com/discourse/mini_racer
107
127
  licenses:
108
128
  - MIT
109
- metadata: {}
110
- post_install_message:
129
+ metadata:
130
+ bug_tracker_uri: https://github.com/discourse/mini_racer/issues
131
+ changelog_uri: https://github.com/discourse/mini_racer/blob/v0.4.0/CHANGELOG
132
+ documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.4.0
133
+ source_code_uri: https://github.com/discourse/mini_racer/tree/v0.4.0
134
+ post_install_message:
111
135
  rdoc_options: []
112
136
  require_paths:
113
137
  - lib
@@ -123,9 +147,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
147
  - !ruby/object:Gem::Version
124
148
  version: '0'
125
149
  requirements: []
126
- rubyforge_project:
127
- rubygems_version: 2.7.6
128
- signing_key:
150
+ rubygems_version: 3.2.2
151
+ signing_key:
129
152
  specification_version: 4
130
153
  summary: Minimal embedded v8 for Ruby
131
154
  test_files: []