mini_racer 0.2.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []