mini_racer 0.3.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 +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +17 -0
- data/CHANGELOG +219 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +471 -0
- data/Rakefile +95 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ext/mini_racer_extension/extconf.rb +64 -0
- data/ext/mini_racer_extension/mini_racer_extension.cc +1714 -0
- data/lib/mini_racer.rb +434 -0
- data/lib/mini_racer/version.rb +5 -0
- data/mini_racer.gemspec +41 -0
- metadata +148 -0
data/Rakefile
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
require "rake/extensiontask"
|
4
|
+
|
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__) + '/mini_racer.gemspec' )
|
14
|
+
Rake::ExtensionTask.new( 'mini_racer_extension', gem )
|
15
|
+
|
16
|
+
|
17
|
+
# via http://blog.flavorjon.es/2009/06/easily-valgrind-gdb-your-ruby-c.html
|
18
|
+
namespace :test do
|
19
|
+
desc "run test suite with Address Sanitizer"
|
20
|
+
task :asan do
|
21
|
+
ENV["CONFIGURE_ARGS"] = [ENV["CONFIGURE_ARGS"], '--enable-asan'].compact.join(' ')
|
22
|
+
Rake::Task['compile'].invoke
|
23
|
+
|
24
|
+
asan_path = `ldconfig -N -p |grep libasan | grep -v 32 | sed 's/.* => \\(.*\\)$/\\1/'`.chomp.split("\n")[-1]
|
25
|
+
|
26
|
+
|
27
|
+
cmdline = "env LD_PRELOAD=\"#{asan_path}\" ruby test/test_leak.rb"
|
28
|
+
puts cmdline
|
29
|
+
system cmdline
|
30
|
+
|
31
|
+
cmdline = "env LD_PRELOAD=\"#{asan_path}\" rake test"
|
32
|
+
puts cmdline
|
33
|
+
system cmdline
|
34
|
+
end
|
35
|
+
# partial-loads-ok and undef-value-errors necessary to ignore
|
36
|
+
# spurious (and eminently ignorable) warnings from the ruby
|
37
|
+
# interpreter
|
38
|
+
VALGRIND_BASIC_OPTS = "--num-callers=50 --error-limit=no \
|
39
|
+
--partial-loads-ok=yes --undef-value-errors=no"
|
40
|
+
|
41
|
+
desc "run test suite under valgrind with basic ruby options"
|
42
|
+
task :valgrind => :compile do
|
43
|
+
cmdline = "valgrind #{VALGRIND_BASIC_OPTS} ruby test/test_leak.rb"
|
44
|
+
puts cmdline
|
45
|
+
system cmdline
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "run test suite under valgrind with leak-check=full"
|
49
|
+
task :valgrind_leak_check => :compile do
|
50
|
+
cmdline = "valgrind #{VALGRIND_BASIC_OPTS} --leak-check=full ruby test/test_leak.rb"
|
51
|
+
puts cmdline
|
52
|
+
require 'open3'
|
53
|
+
_, stderr = Open3.capture3(cmdline)
|
54
|
+
|
55
|
+
section = ""
|
56
|
+
stderr.split("\n").each do |line|
|
57
|
+
|
58
|
+
if line =~ /==.*==\s*$/
|
59
|
+
if (section =~ /mini_racer|SUMMARY/)
|
60
|
+
puts
|
61
|
+
puts section
|
62
|
+
puts
|
63
|
+
end
|
64
|
+
section = ""
|
65
|
+
else
|
66
|
+
section << line << "\n"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
desc 'run clang-tidy linter on mini_racer_extension.cc'
|
73
|
+
task :lint do
|
74
|
+
require 'mkmf'
|
75
|
+
require 'libv8'
|
76
|
+
|
77
|
+
Libv8.configure_makefile
|
78
|
+
|
79
|
+
conf = RbConfig::CONFIG.merge('hdrdir' => $hdrdir.quote, 'srcdir' => $srcdir.quote,
|
80
|
+
'arch_hdrdir' => $arch_hdrdir.quote,
|
81
|
+
'top_srcdir' => $top_srcdir.quote)
|
82
|
+
if $universal and (arch_flag = conf['ARCH_FLAG']) and !arch_flag.empty?
|
83
|
+
conf['ARCH_FLAG'] = arch_flag.gsub(/(?:\G|\s)-arch\s+\S+/, '')
|
84
|
+
end
|
85
|
+
|
86
|
+
checks = %W(bugprone-*
|
87
|
+
cert-*
|
88
|
+
cppcoreguidelines-*
|
89
|
+
clang-analyzer-*
|
90
|
+
performance-*
|
91
|
+
portability-*
|
92
|
+
readability-*).join(',')
|
93
|
+
|
94
|
+
sh RbConfig::expand("clang-tidy -checks='#{checks}' ext/mini_racer_extension/mini_racer_extension.cc -- #$INCFLAGS #$CPPFLAGS", conf)
|
95
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "mini_racer"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
require 'libv8'
|
3
|
+
|
4
|
+
IS_DARWIN = RUBY_PLATFORM =~ /darwin/
|
5
|
+
|
6
|
+
have_library('pthread')
|
7
|
+
have_library('objc') if IS_DARWIN
|
8
|
+
$CPPFLAGS += " -Wall" unless $CPPFLAGS.split.include? "-Wall"
|
9
|
+
$CPPFLAGS += " -g" unless $CPPFLAGS.split.include? "-g"
|
10
|
+
$CPPFLAGS += " -rdynamic" unless $CPPFLAGS.split.include? "-rdynamic"
|
11
|
+
$CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or IS_DARWIN
|
12
|
+
$CPPFLAGS += " -std=c++0x"
|
13
|
+
$CPPFLAGS += " -fpermissive"
|
14
|
+
$CPPFLAGS += " -DV8_COMPRESS_POINTERS"
|
15
|
+
|
16
|
+
$CPPFLAGS += " -Wno-reserved-user-defined-literal" if IS_DARWIN
|
17
|
+
|
18
|
+
$LDFLAGS.insert(0, " -stdlib=libc++ ") if IS_DARWIN
|
19
|
+
|
20
|
+
if ENV['CXX']
|
21
|
+
puts "SETTING CXX"
|
22
|
+
CONFIG['CXX'] = ENV['CXX']
|
23
|
+
end
|
24
|
+
|
25
|
+
CXX11_TEST = <<EOS
|
26
|
+
#if __cplusplus <= 199711L
|
27
|
+
# error A compiler that supports at least C++11 is required in order to compile this project.
|
28
|
+
#endif
|
29
|
+
EOS
|
30
|
+
|
31
|
+
`echo "#{CXX11_TEST}" | #{CONFIG['CXX']} -std=c++0x -x c++ -E -`
|
32
|
+
unless $?.success?
|
33
|
+
warn <<EOS
|
34
|
+
|
35
|
+
|
36
|
+
WARNING: C++11 support is required for compiling mini_racer. Please make sure
|
37
|
+
you are using a compiler that supports at least C++11. Examples of such
|
38
|
+
compilers are GCC 4.7+ and Clang 3.2+.
|
39
|
+
|
40
|
+
If you are using Travis, consider either migrating your build to Ubuntu Trusty or
|
41
|
+
installing GCC 4.8. See mini_racer's README.md for more information.
|
42
|
+
|
43
|
+
|
44
|
+
EOS
|
45
|
+
end
|
46
|
+
|
47
|
+
CONFIG['LDSHARED'] = '$(CXX) -shared' unless IS_DARWIN
|
48
|
+
if CONFIG['warnflags']
|
49
|
+
CONFIG['warnflags'].gsub!('-Wdeclaration-after-statement', '')
|
50
|
+
CONFIG['warnflags'].gsub!('-Wimplicit-function-declaration', '')
|
51
|
+
end
|
52
|
+
|
53
|
+
if enable_config('debug') || enable_config('asan')
|
54
|
+
CONFIG['debugflags'] << ' -ggdb3 -O0'
|
55
|
+
end
|
56
|
+
|
57
|
+
Libv8.configure_makefile
|
58
|
+
|
59
|
+
if enable_config('asan')
|
60
|
+
$CPPFLAGS.insert(0, " -fsanitize=address ")
|
61
|
+
$LDFLAGS.insert(0, " -fsanitize=address ")
|
62
|
+
end
|
63
|
+
|
64
|
+
create_makefile 'mini_racer_extension'
|
@@ -0,0 +1,1714 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include <ruby.h>
|
3
|
+
#include <ruby/thread.h>
|
4
|
+
#include <ruby/io.h>
|
5
|
+
#include <v8.h>
|
6
|
+
#include <v8-profiler.h>
|
7
|
+
#include <libplatform/libplatform.h>
|
8
|
+
#include <ruby/encoding.h>
|
9
|
+
#include <pthread.h>
|
10
|
+
#include <unistd.h>
|
11
|
+
#include <mutex>
|
12
|
+
#include <atomic>
|
13
|
+
#include <math.h>
|
14
|
+
|
15
|
+
using namespace v8;
|
16
|
+
|
17
|
+
typedef struct {
|
18
|
+
const char* data;
|
19
|
+
int raw_size;
|
20
|
+
} SnapshotInfo;
|
21
|
+
|
22
|
+
class IsolateInfo {
|
23
|
+
public:
|
24
|
+
Isolate* isolate;
|
25
|
+
ArrayBuffer::Allocator* allocator;
|
26
|
+
StartupData* startup_data;
|
27
|
+
bool interrupted;
|
28
|
+
bool added_gc_cb;
|
29
|
+
pid_t pid;
|
30
|
+
VALUE mutex;
|
31
|
+
|
32
|
+
class Lock {
|
33
|
+
VALUE &mutex;
|
34
|
+
|
35
|
+
public:
|
36
|
+
Lock(VALUE &mutex) : mutex(mutex) {
|
37
|
+
rb_mutex_lock(mutex);
|
38
|
+
}
|
39
|
+
~Lock() {
|
40
|
+
rb_mutex_unlock(mutex);
|
41
|
+
}
|
42
|
+
};
|
43
|
+
|
44
|
+
|
45
|
+
IsolateInfo() : isolate(nullptr), allocator(nullptr), startup_data(nullptr),
|
46
|
+
interrupted(false), added_gc_cb(false), pid(getpid()), refs_count(0) {
|
47
|
+
VALUE cMutex = rb_const_get(rb_cThread, rb_intern("Mutex"));
|
48
|
+
mutex = rb_class_new_instance(0, nullptr, cMutex);
|
49
|
+
}
|
50
|
+
|
51
|
+
~IsolateInfo();
|
52
|
+
|
53
|
+
void init(SnapshotInfo* snapshot_info = nullptr);
|
54
|
+
|
55
|
+
void mark() {
|
56
|
+
rb_gc_mark(mutex);
|
57
|
+
}
|
58
|
+
|
59
|
+
Lock createLock() {
|
60
|
+
Lock lock(mutex);
|
61
|
+
return lock;
|
62
|
+
}
|
63
|
+
|
64
|
+
void hold() {
|
65
|
+
refs_count++;
|
66
|
+
}
|
67
|
+
void release() {
|
68
|
+
if (--refs_count <= 0) {
|
69
|
+
delete this;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
int refs() {
|
74
|
+
return refs_count;
|
75
|
+
}
|
76
|
+
|
77
|
+
static void* operator new(size_t size) {
|
78
|
+
return ruby_xmalloc(size);
|
79
|
+
}
|
80
|
+
|
81
|
+
static void operator delete(void *block) {
|
82
|
+
xfree(block);
|
83
|
+
}
|
84
|
+
private:
|
85
|
+
// how many references to this isolate exist
|
86
|
+
// we can't rely on Ruby's GC for this, because Ruby could destroy the
|
87
|
+
// isolate before destroying the contexts that depend on them. We'd need to
|
88
|
+
// keep a list of linked contexts in the isolate to destroy those first when
|
89
|
+
// isolate destruction was requested. Keeping such a list would require
|
90
|
+
// notification from the context VALUEs when they are constructed and
|
91
|
+
// destroyed. With a ref count, those notifications are still needed, but
|
92
|
+
// we keep a simple int rather than a list of pointers.
|
93
|
+
std::atomic_int refs_count;
|
94
|
+
};
|
95
|
+
|
96
|
+
typedef struct {
|
97
|
+
IsolateInfo* isolate_info;
|
98
|
+
Persistent<Context>* context;
|
99
|
+
} ContextInfo;
|
100
|
+
|
101
|
+
typedef struct {
|
102
|
+
bool parsed;
|
103
|
+
bool executed;
|
104
|
+
bool terminated;
|
105
|
+
bool json;
|
106
|
+
Persistent<Value>* value;
|
107
|
+
Persistent<Value>* message;
|
108
|
+
Persistent<Value>* backtrace;
|
109
|
+
} EvalResult;
|
110
|
+
|
111
|
+
typedef struct {
|
112
|
+
ContextInfo* context_info;
|
113
|
+
Local<String>* eval;
|
114
|
+
Local<String>* filename;
|
115
|
+
useconds_t timeout;
|
116
|
+
EvalResult* result;
|
117
|
+
size_t max_memory;
|
118
|
+
} EvalParams;
|
119
|
+
|
120
|
+
typedef struct {
|
121
|
+
ContextInfo *context_info;
|
122
|
+
char *function_name;
|
123
|
+
int argc;
|
124
|
+
bool error;
|
125
|
+
Local<Function> fun;
|
126
|
+
Local<Value> *argv;
|
127
|
+
EvalResult result;
|
128
|
+
size_t max_memory;
|
129
|
+
} FunctionCall;
|
130
|
+
|
131
|
+
enum IsolateFlags {
|
132
|
+
IN_GVL,
|
133
|
+
DO_TERMINATE,
|
134
|
+
MEM_SOFTLIMIT_VALUE,
|
135
|
+
MEM_SOFTLIMIT_REACHED,
|
136
|
+
};
|
137
|
+
|
138
|
+
static VALUE rb_cContext;
|
139
|
+
static VALUE rb_cSnapshot;
|
140
|
+
static VALUE rb_cIsolate;
|
141
|
+
|
142
|
+
static VALUE rb_eScriptTerminatedError;
|
143
|
+
static VALUE rb_eV8OutOfMemoryError;
|
144
|
+
static VALUE rb_eParseError;
|
145
|
+
static VALUE rb_eScriptRuntimeError;
|
146
|
+
static VALUE rb_cJavaScriptFunction;
|
147
|
+
static VALUE rb_eSnapshotError;
|
148
|
+
static VALUE rb_ePlatformAlreadyInitializedError;
|
149
|
+
static VALUE rb_mJSON;
|
150
|
+
|
151
|
+
static VALUE rb_cFailedV8Conversion;
|
152
|
+
static VALUE rb_cDateTime = Qnil;
|
153
|
+
|
154
|
+
static std::unique_ptr<Platform> current_platform = NULL;
|
155
|
+
static std::mutex platform_lock;
|
156
|
+
|
157
|
+
static pthread_attr_t *thread_attr_p;
|
158
|
+
static pthread_rwlock_t exit_lock = PTHREAD_RWLOCK_INITIALIZER;
|
159
|
+
static bool ruby_exiting; // guarded by exit_lock
|
160
|
+
|
161
|
+
static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) {
|
162
|
+
bool platform_already_initialized = false;
|
163
|
+
|
164
|
+
if(TYPE(flag_as_str) != T_STRING) {
|
165
|
+
rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE" (should be a string)",
|
166
|
+
rb_obj_class(flag_as_str));
|
167
|
+
}
|
168
|
+
|
169
|
+
platform_lock.lock();
|
170
|
+
|
171
|
+
if (current_platform == NULL) {
|
172
|
+
V8::SetFlagsFromString(RSTRING_PTR(flag_as_str), (int)RSTRING_LEN(flag_as_str));
|
173
|
+
} else {
|
174
|
+
platform_already_initialized = true;
|
175
|
+
}
|
176
|
+
|
177
|
+
platform_lock.unlock();
|
178
|
+
|
179
|
+
// important to raise outside of the lock
|
180
|
+
if (platform_already_initialized) {
|
181
|
+
rb_raise(rb_ePlatformAlreadyInitializedError, "The V8 platform is already initialized");
|
182
|
+
}
|
183
|
+
|
184
|
+
return Qnil;
|
185
|
+
}
|
186
|
+
|
187
|
+
static void init_v8() {
|
188
|
+
// no need to wait for the lock if already initialized
|
189
|
+
if (current_platform != NULL) return;
|
190
|
+
|
191
|
+
platform_lock.lock();
|
192
|
+
|
193
|
+
if (current_platform == NULL) {
|
194
|
+
V8::InitializeICU();
|
195
|
+
current_platform = platform::NewDefaultPlatform();
|
196
|
+
V8::InitializePlatform(current_platform.get());
|
197
|
+
V8::Initialize();
|
198
|
+
}
|
199
|
+
|
200
|
+
platform_lock.unlock();
|
201
|
+
}
|
202
|
+
|
203
|
+
static void gc_callback(Isolate *isolate, GCType type, GCCallbackFlags flags) {
|
204
|
+
if((bool)isolate->GetData(MEM_SOFTLIMIT_REACHED)) return;
|
205
|
+
|
206
|
+
size_t softlimit = *(size_t*) isolate->GetData(MEM_SOFTLIMIT_VALUE);
|
207
|
+
|
208
|
+
HeapStatistics stats;
|
209
|
+
isolate->GetHeapStatistics(&stats);
|
210
|
+
size_t used = stats.used_heap_size();
|
211
|
+
|
212
|
+
if(used > softlimit) {
|
213
|
+
isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)true);
|
214
|
+
isolate->TerminateExecution();
|
215
|
+
}
|
216
|
+
}
|
217
|
+
|
218
|
+
// to be called with active lock and scope
|
219
|
+
static void prepare_result(MaybeLocal<Value> v8res,
|
220
|
+
TryCatch& trycatch,
|
221
|
+
Isolate* isolate,
|
222
|
+
Local<Context> context,
|
223
|
+
EvalResult& evalRes /* out */) {
|
224
|
+
|
225
|
+
// just don't touch .parsed
|
226
|
+
evalRes.terminated = false;
|
227
|
+
evalRes.json = false;
|
228
|
+
evalRes.value = nullptr;
|
229
|
+
evalRes.message = nullptr;
|
230
|
+
evalRes.backtrace = nullptr;
|
231
|
+
evalRes.executed = !v8res.IsEmpty();
|
232
|
+
|
233
|
+
if (evalRes.executed) {
|
234
|
+
// arrays and objects get converted to json
|
235
|
+
Local<Value> local_value = v8res.ToLocalChecked();
|
236
|
+
if ((local_value->IsObject() || local_value->IsArray()) &&
|
237
|
+
!local_value->IsDate() && !local_value->IsFunction()) {
|
238
|
+
Local<Object> JSON = context->Global()->Get(
|
239
|
+
context, String::NewFromUtf8Literal(isolate, "JSON"))
|
240
|
+
.ToLocalChecked().As<Object>();
|
241
|
+
|
242
|
+
Local<Function> stringify = JSON->Get(
|
243
|
+
context, v8::String::NewFromUtf8Literal(isolate, "stringify"))
|
244
|
+
.ToLocalChecked().As<Function>();
|
245
|
+
|
246
|
+
Local<Object> object = local_value->ToObject(context).ToLocalChecked();
|
247
|
+
const unsigned argc = 1;
|
248
|
+
Local<Value> argv[argc] = { object };
|
249
|
+
MaybeLocal<Value> json = stringify->Call(context, JSON, argc, argv);
|
250
|
+
|
251
|
+
if (json.IsEmpty()) {
|
252
|
+
evalRes.executed = false;
|
253
|
+
} else {
|
254
|
+
evalRes.json = true;
|
255
|
+
Persistent<Value>* persistent = new Persistent<Value>();
|
256
|
+
persistent->Reset(isolate, json.ToLocalChecked());
|
257
|
+
evalRes.value = persistent;
|
258
|
+
}
|
259
|
+
|
260
|
+
} else {
|
261
|
+
Persistent<Value>* persistent = new Persistent<Value>();
|
262
|
+
persistent->Reset(isolate, local_value);
|
263
|
+
evalRes.value = persistent;
|
264
|
+
}
|
265
|
+
}
|
266
|
+
|
267
|
+
if (!evalRes.executed || !evalRes.parsed) {
|
268
|
+
if (trycatch.HasCaught()) {
|
269
|
+
if (!trycatch.Exception()->IsNull()) {
|
270
|
+
evalRes.message = new Persistent<Value>();
|
271
|
+
Local<Message> message = trycatch.Message();
|
272
|
+
char buf[1000];
|
273
|
+
int len, line, column;
|
274
|
+
|
275
|
+
if (!message->GetLineNumber(context).To(&line)) {
|
276
|
+
line = 0;
|
277
|
+
}
|
278
|
+
|
279
|
+
if (!message->GetStartColumn(context).To(&column)) {
|
280
|
+
column = 0;
|
281
|
+
}
|
282
|
+
|
283
|
+
len = snprintf(buf, sizeof(buf), "%s at %s:%i:%i", *String::Utf8Value(isolate, message->Get()),
|
284
|
+
*String::Utf8Value(isolate, message->GetScriptResourceName()->ToString(context).ToLocalChecked()),
|
285
|
+
line,
|
286
|
+
column);
|
287
|
+
|
288
|
+
if ((size_t) len >= sizeof(buf)) {
|
289
|
+
len = sizeof(buf) - 1;
|
290
|
+
buf[len] = '\0';
|
291
|
+
}
|
292
|
+
|
293
|
+
Local<String> v8_message = String::NewFromUtf8(isolate, buf, NewStringType::kNormal, len).ToLocalChecked();
|
294
|
+
evalRes.message->Reset(isolate, v8_message);
|
295
|
+
} else if(trycatch.HasTerminated()) {
|
296
|
+
evalRes.terminated = true;
|
297
|
+
evalRes.message = new Persistent<Value>();
|
298
|
+
Local<String> tmp = String::NewFromUtf8Literal(isolate, "JavaScript was terminated (either by timeout or explicitly)");
|
299
|
+
evalRes.message->Reset(isolate, tmp);
|
300
|
+
}
|
301
|
+
if (!trycatch.StackTrace(context).IsEmpty()) {
|
302
|
+
evalRes.backtrace = new Persistent<Value>();
|
303
|
+
evalRes.backtrace->Reset(isolate,
|
304
|
+
trycatch.StackTrace(context).ToLocalChecked()->ToString(context).ToLocalChecked());
|
305
|
+
}
|
306
|
+
}
|
307
|
+
}
|
308
|
+
}
|
309
|
+
|
310
|
+
void*
|
311
|
+
nogvl_context_eval(void* arg) {
|
312
|
+
|
313
|
+
EvalParams* eval_params = (EvalParams*)arg;
|
314
|
+
EvalResult* result = eval_params->result;
|
315
|
+
IsolateInfo* isolate_info = eval_params->context_info->isolate_info;
|
316
|
+
Isolate* isolate = isolate_info->isolate;
|
317
|
+
|
318
|
+
Isolate::Scope isolate_scope(isolate);
|
319
|
+
HandleScope handle_scope(isolate);
|
320
|
+
TryCatch trycatch(isolate);
|
321
|
+
Local<Context> context = eval_params->context_info->context->Get(isolate);
|
322
|
+
Context::Scope context_scope(context);
|
323
|
+
v8::ScriptOrigin *origin = NULL;
|
324
|
+
|
325
|
+
// in gvl flag
|
326
|
+
isolate->SetData(IN_GVL, (void*)false);
|
327
|
+
// terminate ASAP
|
328
|
+
isolate->SetData(DO_TERMINATE, (void*)false);
|
329
|
+
// Memory softlimit
|
330
|
+
isolate->SetData(MEM_SOFTLIMIT_VALUE, (void*)false);
|
331
|
+
// Memory softlimit hit flag
|
332
|
+
isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)false);
|
333
|
+
|
334
|
+
MaybeLocal<Script> parsed_script;
|
335
|
+
|
336
|
+
if (eval_params->filename) {
|
337
|
+
origin = new v8::ScriptOrigin(*eval_params->filename);
|
338
|
+
}
|
339
|
+
|
340
|
+
parsed_script = Script::Compile(context, *eval_params->eval, origin);
|
341
|
+
|
342
|
+
if (origin) {
|
343
|
+
delete origin;
|
344
|
+
}
|
345
|
+
|
346
|
+
result->parsed = !parsed_script.IsEmpty();
|
347
|
+
result->executed = false;
|
348
|
+
result->terminated = false;
|
349
|
+
result->json = false;
|
350
|
+
result->value = NULL;
|
351
|
+
|
352
|
+
MaybeLocal<Value> maybe_value;
|
353
|
+
if (!result->parsed) {
|
354
|
+
result->message = new Persistent<Value>();
|
355
|
+
result->message->Reset(isolate, trycatch.Exception());
|
356
|
+
} else {
|
357
|
+
// parsing successful
|
358
|
+
if (eval_params->max_memory > 0) {
|
359
|
+
isolate->SetData(MEM_SOFTLIMIT_VALUE, &eval_params->max_memory);
|
360
|
+
if (!isolate_info->added_gc_cb) {
|
361
|
+
isolate->AddGCEpilogueCallback(gc_callback);
|
362
|
+
isolate_info->added_gc_cb = true;
|
363
|
+
}
|
364
|
+
}
|
365
|
+
|
366
|
+
maybe_value = parsed_script.ToLocalChecked()->Run(context);
|
367
|
+
}
|
368
|
+
|
369
|
+
prepare_result(maybe_value, trycatch, isolate, context, *result);
|
370
|
+
|
371
|
+
isolate->SetData(IN_GVL, (void*)true);
|
372
|
+
|
373
|
+
return NULL;
|
374
|
+
}
|
375
|
+
|
376
|
+
static VALUE new_empty_failed_conv_obj() {
|
377
|
+
// TODO isolate code that translates execption to ruby
|
378
|
+
// exception so we can properly return it
|
379
|
+
return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
|
380
|
+
}
|
381
|
+
|
382
|
+
// assumes isolate locking is in place
|
383
|
+
static VALUE convert_v8_to_ruby(Isolate* isolate, Local<Context> context,
|
384
|
+
Local<Value> value) {
|
385
|
+
|
386
|
+
Isolate::Scope isolate_scope(isolate);
|
387
|
+
HandleScope scope(isolate);
|
388
|
+
|
389
|
+
if (value->IsNull() || value->IsUndefined()){
|
390
|
+
return Qnil;
|
391
|
+
}
|
392
|
+
|
393
|
+
if (value->IsInt32()) {
|
394
|
+
return INT2FIX(value->Int32Value(context).ToChecked());
|
395
|
+
}
|
396
|
+
|
397
|
+
if (value->IsNumber()) {
|
398
|
+
return rb_float_new(value->NumberValue(context).ToChecked());
|
399
|
+
}
|
400
|
+
|
401
|
+
if (value->IsTrue()) {
|
402
|
+
return Qtrue;
|
403
|
+
}
|
404
|
+
|
405
|
+
if (value->IsFalse()) {
|
406
|
+
return Qfalse;
|
407
|
+
}
|
408
|
+
|
409
|
+
if (value->IsArray()) {
|
410
|
+
VALUE rb_array = rb_ary_new();
|
411
|
+
Local<Array> arr = Local<Array>::Cast(value);
|
412
|
+
for(uint32_t i=0; i < arr->Length(); i++) {
|
413
|
+
MaybeLocal<Value> element = arr->Get(context, i);
|
414
|
+
if (element.IsEmpty()) {
|
415
|
+
continue;
|
416
|
+
}
|
417
|
+
VALUE rb_elem = convert_v8_to_ruby(isolate, context, element.ToLocalChecked());
|
418
|
+
if (rb_funcall(rb_elem, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
|
419
|
+
return rb_elem;
|
420
|
+
}
|
421
|
+
rb_ary_push(rb_array, rb_elem);
|
422
|
+
}
|
423
|
+
return rb_array;
|
424
|
+
}
|
425
|
+
|
426
|
+
if (value->IsFunction()){
|
427
|
+
return rb_funcall(rb_cJavaScriptFunction, rb_intern("new"), 0);
|
428
|
+
}
|
429
|
+
|
430
|
+
if (value->IsDate()){
|
431
|
+
double ts = Local<Date>::Cast(value)->ValueOf();
|
432
|
+
double secs = ts/1000;
|
433
|
+
long nanos = round((secs - floor(secs)) * 1000000);
|
434
|
+
|
435
|
+
return rb_time_new(secs, nanos);
|
436
|
+
}
|
437
|
+
|
438
|
+
if (value->IsObject()) {
|
439
|
+
VALUE rb_hash = rb_hash_new();
|
440
|
+
TryCatch trycatch(isolate);
|
441
|
+
|
442
|
+
Local<Object> object = value->ToObject(context).ToLocalChecked();
|
443
|
+
auto maybe_props = object->GetOwnPropertyNames(context);
|
444
|
+
if (!maybe_props.IsEmpty()) {
|
445
|
+
Local<Array> props = maybe_props.ToLocalChecked();
|
446
|
+
for(uint32_t i=0; i < props->Length(); i++) {
|
447
|
+
MaybeLocal<Value> key = props->Get(context, i);
|
448
|
+
if (key.IsEmpty()) {
|
449
|
+
return rb_funcall(rb_cFailedV8Conversion, rb_intern("new"), 1, rb_str_new2(""));
|
450
|
+
}
|
451
|
+
VALUE rb_key = convert_v8_to_ruby(isolate, context, key.ToLocalChecked());
|
452
|
+
|
453
|
+
MaybeLocal<Value> prop_value = object->Get(context, key.ToLocalChecked());
|
454
|
+
// this may have failed due to Get raising
|
455
|
+
if (prop_value.IsEmpty() || trycatch.HasCaught()) {
|
456
|
+
return new_empty_failed_conv_obj();
|
457
|
+
}
|
458
|
+
|
459
|
+
VALUE rb_value = convert_v8_to_ruby(
|
460
|
+
isolate, context, prop_value.ToLocalChecked());
|
461
|
+
rb_hash_aset(rb_hash, rb_key, rb_value);
|
462
|
+
}
|
463
|
+
}
|
464
|
+
return rb_hash;
|
465
|
+
}
|
466
|
+
|
467
|
+
if (value->IsSymbol()) {
|
468
|
+
v8::String::Utf8Value symbol_name(isolate,
|
469
|
+
Local<Symbol>::Cast(value)->Name());
|
470
|
+
|
471
|
+
VALUE str_symbol = rb_enc_str_new(
|
472
|
+
*symbol_name,
|
473
|
+
symbol_name.length(),
|
474
|
+
rb_enc_find("utf-8")
|
475
|
+
);
|
476
|
+
|
477
|
+
return ID2SYM(rb_intern_str(str_symbol));
|
478
|
+
}
|
479
|
+
|
480
|
+
MaybeLocal<String> rstr_maybe = value->ToString(context);
|
481
|
+
|
482
|
+
if (rstr_maybe.IsEmpty()) {
|
483
|
+
return Qnil;
|
484
|
+
} else {
|
485
|
+
Local<String> rstr = rstr_maybe.ToLocalChecked();
|
486
|
+
return rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
|
487
|
+
}
|
488
|
+
}
|
489
|
+
|
490
|
+
static VALUE convert_v8_to_ruby(Isolate* isolate,
|
491
|
+
const Persistent<Context>& context,
|
492
|
+
Local<Value> value) {
|
493
|
+
HandleScope scope(isolate);
|
494
|
+
return convert_v8_to_ruby(isolate,
|
495
|
+
Local<Context>::New(isolate, context),
|
496
|
+
value);
|
497
|
+
}
|
498
|
+
|
499
|
+
static VALUE convert_v8_to_ruby(Isolate* isolate,
|
500
|
+
const Persistent<Context>& context,
|
501
|
+
const Persistent<Value>& value) {
|
502
|
+
HandleScope scope(isolate);
|
503
|
+
return convert_v8_to_ruby(isolate,
|
504
|
+
Local<Context>::New(isolate, context),
|
505
|
+
Local<Value>::New(isolate, value));
|
506
|
+
}
|
507
|
+
|
508
|
+
static Local<Value> convert_ruby_to_v8(Isolate* isolate, Local<Context> context, VALUE value) {
|
509
|
+
EscapableHandleScope scope(isolate);
|
510
|
+
|
511
|
+
Local<Array> array;
|
512
|
+
Local<Object> object;
|
513
|
+
VALUE hash_as_array;
|
514
|
+
VALUE pair;
|
515
|
+
int i;
|
516
|
+
long length;
|
517
|
+
long fixnum;
|
518
|
+
VALUE klass;
|
519
|
+
|
520
|
+
switch (TYPE(value)) {
|
521
|
+
case T_FIXNUM:
|
522
|
+
fixnum = NUM2LONG(value);
|
523
|
+
if (fixnum > INT_MAX)
|
524
|
+
{
|
525
|
+
return scope.Escape(Number::New(isolate, (double)fixnum));
|
526
|
+
}
|
527
|
+
return scope.Escape(Integer::New(isolate, (int)fixnum));
|
528
|
+
case T_FLOAT:
|
529
|
+
return scope.Escape(Number::New(isolate, NUM2DBL(value)));
|
530
|
+
case T_STRING:
|
531
|
+
return scope.Escape(String::NewFromUtf8(isolate, RSTRING_PTR(value), NewStringType::kNormal, (int)RSTRING_LEN(value)).ToLocalChecked());
|
532
|
+
case T_NIL:
|
533
|
+
return scope.Escape(Null(isolate));
|
534
|
+
case T_TRUE:
|
535
|
+
return scope.Escape(True(isolate));
|
536
|
+
case T_FALSE:
|
537
|
+
return scope.Escape(False(isolate));
|
538
|
+
case T_ARRAY:
|
539
|
+
length = RARRAY_LEN(value);
|
540
|
+
array = Array::New(isolate, (int)length);
|
541
|
+
for(i=0; i<length; i++) {
|
542
|
+
array->Set(context, i, convert_ruby_to_v8(isolate, context, rb_ary_entry(value, i)));
|
543
|
+
}
|
544
|
+
return scope.Escape(array);
|
545
|
+
case T_HASH:
|
546
|
+
object = Object::New(isolate);
|
547
|
+
hash_as_array = rb_funcall(value, rb_intern("to_a"), 0);
|
548
|
+
length = RARRAY_LEN(hash_as_array);
|
549
|
+
for(i=0; i<length; i++) {
|
550
|
+
pair = rb_ary_entry(hash_as_array, i);
|
551
|
+
object->Set(context, convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 0)),
|
552
|
+
convert_ruby_to_v8(isolate, context, rb_ary_entry(pair, 1)));
|
553
|
+
}
|
554
|
+
return scope.Escape(object);
|
555
|
+
case T_SYMBOL:
|
556
|
+
value = rb_funcall(value, rb_intern("to_s"), 0);
|
557
|
+
return scope.Escape(String::NewFromUtf8(isolate, RSTRING_PTR(value), NewStringType::kNormal, (int)RSTRING_LEN(value)).ToLocalChecked());
|
558
|
+
case T_DATA:
|
559
|
+
klass = rb_funcall(value, rb_intern("class"), 0);
|
560
|
+
if (klass == rb_cTime || klass == rb_cDateTime)
|
561
|
+
{
|
562
|
+
if (klass == rb_cDateTime)
|
563
|
+
{
|
564
|
+
value = rb_funcall(value, rb_intern("to_time"), 0);
|
565
|
+
}
|
566
|
+
value = rb_funcall(value, rb_intern("to_f"), 0);
|
567
|
+
return scope.Escape(Date::New(context, NUM2DBL(value) * 1000).ToLocalChecked());
|
568
|
+
}
|
569
|
+
case T_OBJECT:
|
570
|
+
case T_CLASS:
|
571
|
+
case T_ICLASS:
|
572
|
+
case T_MODULE:
|
573
|
+
case T_REGEXP:
|
574
|
+
case T_MATCH:
|
575
|
+
case T_STRUCT:
|
576
|
+
case T_BIGNUM:
|
577
|
+
case T_FILE:
|
578
|
+
case T_UNDEF:
|
579
|
+
case T_NODE:
|
580
|
+
default:
|
581
|
+
return scope.Escape(String::NewFromUtf8Literal(isolate, "Undefined Conversion"));
|
582
|
+
}
|
583
|
+
}
|
584
|
+
|
585
|
+
static void unblock_eval(void *ptr) {
|
586
|
+
EvalParams* eval = (EvalParams*)ptr;
|
587
|
+
eval->context_info->isolate_info->interrupted = true;
|
588
|
+
}
|
589
|
+
|
590
|
+
/*
|
591
|
+
* The implementations of the run_extra_code(), create_snapshot_data_blob() and
|
592
|
+
* warm_up_snapshot_data_blob() functions have been derived from V8's test suite.
|
593
|
+
*/
|
594
|
+
static bool run_extra_code(Isolate *isolate, Local<v8::Context> context,
|
595
|
+
const char *utf8_source, const char *name) {
|
596
|
+
Context::Scope context_scope(context);
|
597
|
+
TryCatch try_catch(isolate);
|
598
|
+
Local<String> source_string;
|
599
|
+
if (!String::NewFromUtf8(isolate, utf8_source).ToLocal(&source_string)) {
|
600
|
+
return false;
|
601
|
+
}
|
602
|
+
Local<String> resource_name =
|
603
|
+
String::NewFromUtf8(isolate, name).ToLocalChecked();
|
604
|
+
ScriptOrigin origin(resource_name);
|
605
|
+
ScriptCompiler::Source source(source_string, origin);
|
606
|
+
Local<Script> script;
|
607
|
+
if (!ScriptCompiler::Compile(context, &source).ToLocal(&script))
|
608
|
+
return false;
|
609
|
+
if (script->Run(context).IsEmpty()) return false;
|
610
|
+
return true;
|
611
|
+
}
|
612
|
+
|
613
|
+
static StartupData
|
614
|
+
create_snapshot_data_blob(const char *embedded_source = nullptr) {
|
615
|
+
Isolate *isolate = Isolate::Allocate();
|
616
|
+
|
617
|
+
// Optionally run a script to embed, and serialize to create a snapshot blob.
|
618
|
+
SnapshotCreator snapshot_creator(isolate);
|
619
|
+
{
|
620
|
+
HandleScope scope(isolate);
|
621
|
+
Local<v8::Context> context = v8::Context::New(isolate);
|
622
|
+
if (embedded_source != nullptr &&
|
623
|
+
!run_extra_code(isolate, context, embedded_source, "<embedded>")) {
|
624
|
+
return {};
|
625
|
+
}
|
626
|
+
snapshot_creator.SetDefaultContext(context);
|
627
|
+
}
|
628
|
+
return snapshot_creator.CreateBlob(
|
629
|
+
SnapshotCreator::FunctionCodeHandling::kClear);
|
630
|
+
}
|
631
|
+
|
632
|
+
StartupData warm_up_snapshot_data_blob(StartupData cold_snapshot_blob,
|
633
|
+
const char *warmup_source) {
|
634
|
+
// Use following steps to create a warmed up snapshot blob from a cold one:
|
635
|
+
// - Create a new isolate from the cold snapshot.
|
636
|
+
// - Create a new context to run the warmup script. This will trigger
|
637
|
+
// compilation of executed functions.
|
638
|
+
// - Create a new context. This context will be unpolluted.
|
639
|
+
// - Serialize the isolate and the second context into a new snapshot blob.
|
640
|
+
StartupData result = {nullptr, 0};
|
641
|
+
|
642
|
+
if (cold_snapshot_blob.raw_size > 0 && cold_snapshot_blob.data != nullptr &&
|
643
|
+
warmup_source != NULL) {
|
644
|
+
SnapshotCreator snapshot_creator(nullptr, &cold_snapshot_blob);
|
645
|
+
Isolate *isolate = snapshot_creator.GetIsolate();
|
646
|
+
{
|
647
|
+
HandleScope scope(isolate);
|
648
|
+
Local<Context> context = Context::New(isolate);
|
649
|
+
if (!run_extra_code(isolate, context, warmup_source, "<warm-up>")) {
|
650
|
+
return result;
|
651
|
+
}
|
652
|
+
}
|
653
|
+
{
|
654
|
+
HandleScope handle_scope(isolate);
|
655
|
+
isolate->ContextDisposedNotification(false);
|
656
|
+
Local<Context> context = Context::New(isolate);
|
657
|
+
snapshot_creator.SetDefaultContext(context);
|
658
|
+
}
|
659
|
+
result = snapshot_creator.CreateBlob(
|
660
|
+
SnapshotCreator::FunctionCodeHandling::kKeep);
|
661
|
+
}
|
662
|
+
return result;
|
663
|
+
}
|
664
|
+
|
665
|
+
static VALUE rb_snapshot_size(VALUE self, VALUE str) {
|
666
|
+
SnapshotInfo* snapshot_info;
|
667
|
+
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
|
668
|
+
|
669
|
+
return INT2NUM(snapshot_info->raw_size);
|
670
|
+
}
|
671
|
+
|
672
|
+
static VALUE rb_snapshot_load(VALUE self, VALUE str) {
|
673
|
+
SnapshotInfo* snapshot_info;
|
674
|
+
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
|
675
|
+
|
676
|
+
if(TYPE(str) != T_STRING) {
|
677
|
+
rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
|
678
|
+
rb_obj_class(str));
|
679
|
+
}
|
680
|
+
|
681
|
+
init_v8();
|
682
|
+
|
683
|
+
StartupData startup_data = create_snapshot_data_blob(RSTRING_PTR(str));
|
684
|
+
|
685
|
+
if (startup_data.data == NULL && startup_data.raw_size == 0) {
|
686
|
+
rb_raise(rb_eSnapshotError, "Could not create snapshot, most likely the source is incorrect");
|
687
|
+
}
|
688
|
+
|
689
|
+
snapshot_info->data = startup_data.data;
|
690
|
+
snapshot_info->raw_size = startup_data.raw_size;
|
691
|
+
|
692
|
+
return Qnil;
|
693
|
+
}
|
694
|
+
|
695
|
+
static VALUE rb_snapshot_dump(VALUE self, VALUE str) {
|
696
|
+
SnapshotInfo* snapshot_info;
|
697
|
+
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
|
698
|
+
|
699
|
+
return rb_str_new(snapshot_info->data, snapshot_info->raw_size);
|
700
|
+
}
|
701
|
+
|
702
|
+
static VALUE rb_snapshot_warmup_unsafe(VALUE self, VALUE str) {
|
703
|
+
SnapshotInfo* snapshot_info;
|
704
|
+
Data_Get_Struct(self, SnapshotInfo, snapshot_info);
|
705
|
+
|
706
|
+
if(TYPE(str) != T_STRING) {
|
707
|
+
rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
|
708
|
+
rb_obj_class(str));
|
709
|
+
}
|
710
|
+
|
711
|
+
init_v8();
|
712
|
+
|
713
|
+
StartupData cold_startup_data = {snapshot_info->data, snapshot_info->raw_size};
|
714
|
+
StartupData warm_startup_data = warm_up_snapshot_data_blob(cold_startup_data, RSTRING_PTR(str));
|
715
|
+
|
716
|
+
if (warm_startup_data.data == NULL && warm_startup_data.raw_size == 0) {
|
717
|
+
rb_raise(rb_eSnapshotError, "Could not warm up snapshot, most likely the source is incorrect");
|
718
|
+
} else {
|
719
|
+
delete[] snapshot_info->data;
|
720
|
+
|
721
|
+
snapshot_info->data = warm_startup_data.data;
|
722
|
+
snapshot_info->raw_size = warm_startup_data.raw_size;
|
723
|
+
}
|
724
|
+
|
725
|
+
return self;
|
726
|
+
}
|
727
|
+
|
728
|
+
void IsolateInfo::init(SnapshotInfo* snapshot_info) {
|
729
|
+
allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
|
730
|
+
|
731
|
+
Isolate::CreateParams create_params;
|
732
|
+
create_params.array_buffer_allocator = allocator;
|
733
|
+
|
734
|
+
if (snapshot_info) {
|
735
|
+
int raw_size = snapshot_info->raw_size;
|
736
|
+
char* data = new char[raw_size];
|
737
|
+
memcpy(data, snapshot_info->data, raw_size);
|
738
|
+
|
739
|
+
startup_data = new StartupData;
|
740
|
+
startup_data->data = data;
|
741
|
+
startup_data->raw_size = raw_size;
|
742
|
+
|
743
|
+
create_params.snapshot_blob = startup_data;
|
744
|
+
}
|
745
|
+
|
746
|
+
isolate = Isolate::New(create_params);
|
747
|
+
}
|
748
|
+
|
749
|
+
static VALUE rb_isolate_init_with_snapshot(VALUE self, VALUE snapshot) {
|
750
|
+
IsolateInfo* isolate_info;
|
751
|
+
Data_Get_Struct(self, IsolateInfo, isolate_info);
|
752
|
+
|
753
|
+
init_v8();
|
754
|
+
|
755
|
+
SnapshotInfo* snapshot_info = nullptr;
|
756
|
+
if (!NIL_P(snapshot)) {
|
757
|
+
Data_Get_Struct(snapshot, SnapshotInfo, snapshot_info);
|
758
|
+
}
|
759
|
+
|
760
|
+
isolate_info->init(snapshot_info);
|
761
|
+
isolate_info->hold();
|
762
|
+
|
763
|
+
return Qnil;
|
764
|
+
}
|
765
|
+
|
766
|
+
static VALUE rb_isolate_idle_notification(VALUE self, VALUE idle_time_in_ms) {
|
767
|
+
IsolateInfo* isolate_info;
|
768
|
+
Data_Get_Struct(self, IsolateInfo, isolate_info);
|
769
|
+
|
770
|
+
if (current_platform == NULL) return Qfalse;
|
771
|
+
|
772
|
+
double duration = NUM2DBL(idle_time_in_ms) / 1000.0;
|
773
|
+
double now = current_platform->MonotonicallyIncreasingTime();
|
774
|
+
return isolate_info->isolate->IdleNotificationDeadline(now + duration) ? Qtrue : Qfalse;
|
775
|
+
}
|
776
|
+
|
777
|
+
static VALUE rb_isolate_low_memory_notification(VALUE self) {
|
778
|
+
IsolateInfo* isolate_info;
|
779
|
+
Data_Get_Struct(self, IsolateInfo, isolate_info);
|
780
|
+
|
781
|
+
if (current_platform == NULL) return Qfalse;
|
782
|
+
|
783
|
+
isolate_info->isolate->LowMemoryNotification();
|
784
|
+
return Qnil;
|
785
|
+
}
|
786
|
+
|
787
|
+
static VALUE rb_isolate_pump_message_loop(VALUE self) {
|
788
|
+
IsolateInfo* isolate_info;
|
789
|
+
Data_Get_Struct(self, IsolateInfo, isolate_info);
|
790
|
+
|
791
|
+
if (current_platform == NULL) return Qfalse;
|
792
|
+
|
793
|
+
if (platform::PumpMessageLoop(current_platform.get(), isolate_info->isolate)){
|
794
|
+
return Qtrue;
|
795
|
+
} else {
|
796
|
+
return Qfalse;
|
797
|
+
}
|
798
|
+
}
|
799
|
+
|
800
|
+
static VALUE rb_context_init_unsafe(VALUE self, VALUE isolate, VALUE snap) {
|
801
|
+
ContextInfo* context_info;
|
802
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
803
|
+
|
804
|
+
init_v8();
|
805
|
+
|
806
|
+
IsolateInfo* isolate_info;
|
807
|
+
|
808
|
+
if (NIL_P(isolate) || !rb_obj_is_kind_of(isolate, rb_cIsolate)) {
|
809
|
+
isolate_info = new IsolateInfo();
|
810
|
+
|
811
|
+
SnapshotInfo *snapshot_info = nullptr;
|
812
|
+
if (!NIL_P(snap) && rb_obj_is_kind_of(snap, rb_cSnapshot)) {
|
813
|
+
Data_Get_Struct(snap, SnapshotInfo, snapshot_info);
|
814
|
+
}
|
815
|
+
isolate_info->init(snapshot_info);
|
816
|
+
} else { // given isolate or snapshot
|
817
|
+
Data_Get_Struct(isolate, IsolateInfo, isolate_info);
|
818
|
+
}
|
819
|
+
|
820
|
+
context_info->isolate_info = isolate_info;
|
821
|
+
isolate_info->hold();
|
822
|
+
|
823
|
+
{
|
824
|
+
// the ruby lock is needed if this isn't a new isolate
|
825
|
+
IsolateInfo::Lock ruby_lock(isolate_info->mutex);
|
826
|
+
Locker lock(isolate_info->isolate);
|
827
|
+
Isolate::Scope isolate_scope(isolate_info->isolate);
|
828
|
+
HandleScope handle_scope(isolate_info->isolate);
|
829
|
+
|
830
|
+
Local<Context> context = Context::New(isolate_info->isolate);
|
831
|
+
|
832
|
+
context_info->context = new Persistent<Context>();
|
833
|
+
context_info->context->Reset(isolate_info->isolate, context);
|
834
|
+
}
|
835
|
+
|
836
|
+
if (Qnil == rb_cDateTime && rb_funcall(rb_cObject, rb_intern("const_defined?"), 1, rb_str_new2("DateTime")) == Qtrue)
|
837
|
+
{
|
838
|
+
rb_cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
|
839
|
+
}
|
840
|
+
|
841
|
+
return Qnil;
|
842
|
+
}
|
843
|
+
|
844
|
+
static VALUE convert_result_to_ruby(VALUE self /* context */,
|
845
|
+
EvalResult& result) {
|
846
|
+
ContextInfo *context_info;
|
847
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
848
|
+
|
849
|
+
Isolate *isolate = context_info->isolate_info->isolate;
|
850
|
+
Persistent<Context> *p_ctx = context_info->context;
|
851
|
+
|
852
|
+
VALUE message = Qnil;
|
853
|
+
VALUE backtrace = Qnil;
|
854
|
+
{
|
855
|
+
Locker lock(isolate);
|
856
|
+
if (result.message) {
|
857
|
+
message = convert_v8_to_ruby(isolate, *p_ctx, *result.message);
|
858
|
+
result.message->Reset();
|
859
|
+
delete result.message;
|
860
|
+
result.message = nullptr;
|
861
|
+
}
|
862
|
+
|
863
|
+
if (result.backtrace) {
|
864
|
+
backtrace = convert_v8_to_ruby(isolate, *p_ctx, *result.backtrace);
|
865
|
+
result.backtrace->Reset();
|
866
|
+
delete result.backtrace;
|
867
|
+
}
|
868
|
+
}
|
869
|
+
|
870
|
+
// NOTE: this is very important, we can not do an rb_raise from within
|
871
|
+
// a v8 scope, if we do the scope is never cleaned up properly and we leak
|
872
|
+
if (!result.parsed) {
|
873
|
+
if(TYPE(message) == T_STRING) {
|
874
|
+
rb_raise(rb_eParseError, "%s", RSTRING_PTR(message));
|
875
|
+
} else {
|
876
|
+
rb_raise(rb_eParseError, "Unknown JavaScript Error during parse");
|
877
|
+
}
|
878
|
+
}
|
879
|
+
|
880
|
+
if (!result.executed) {
|
881
|
+
VALUE ruby_exception = rb_iv_get(self, "@current_exception");
|
882
|
+
if (ruby_exception == Qnil) {
|
883
|
+
bool mem_softlimit_reached = (bool)isolate->GetData(MEM_SOFTLIMIT_REACHED);
|
884
|
+
// If we were terminated or have the memory softlimit flag set
|
885
|
+
if (result.terminated || mem_softlimit_reached) {
|
886
|
+
ruby_exception = mem_softlimit_reached ? rb_eV8OutOfMemoryError : rb_eScriptTerminatedError;
|
887
|
+
} else {
|
888
|
+
ruby_exception = rb_eScriptRuntimeError;
|
889
|
+
}
|
890
|
+
|
891
|
+
// exception report about what happened
|
892
|
+
if (TYPE(backtrace) == T_STRING) {
|
893
|
+
rb_raise(ruby_exception, "%s", RSTRING_PTR(backtrace));
|
894
|
+
} else if(TYPE(message) == T_STRING) {
|
895
|
+
rb_raise(ruby_exception, "%s", RSTRING_PTR(message));
|
896
|
+
} else {
|
897
|
+
rb_raise(ruby_exception, "Unknown JavaScript Error during execution");
|
898
|
+
}
|
899
|
+
} else {
|
900
|
+
VALUE rb_str = rb_funcall(ruby_exception, rb_intern("to_s"), 0);
|
901
|
+
rb_raise(CLASS_OF(ruby_exception), "%s", RSTRING_PTR(rb_str));
|
902
|
+
}
|
903
|
+
}
|
904
|
+
|
905
|
+
VALUE ret = Qnil;
|
906
|
+
|
907
|
+
// New scope for return value
|
908
|
+
{
|
909
|
+
Locker lock(isolate);
|
910
|
+
Isolate::Scope isolate_scope(isolate);
|
911
|
+
HandleScope handle_scope(isolate);
|
912
|
+
|
913
|
+
Local<Value> tmp = Local<Value>::New(isolate, *result.value);
|
914
|
+
|
915
|
+
if (result.json) {
|
916
|
+
Local<String> rstr = tmp->ToString(p_ctx->Get(isolate)).ToLocalChecked();
|
917
|
+
VALUE json_string = rb_enc_str_new(*String::Utf8Value(isolate, rstr), rstr->Utf8Length(isolate), rb_enc_find("utf-8"));
|
918
|
+
ret = rb_funcall(rb_mJSON, rb_intern("parse"), 1, json_string);
|
919
|
+
} else {
|
920
|
+
ret = convert_v8_to_ruby(isolate, *p_ctx, tmp);
|
921
|
+
}
|
922
|
+
|
923
|
+
result.value->Reset();
|
924
|
+
delete result.value;
|
925
|
+
}
|
926
|
+
|
927
|
+
if (rb_funcall(ret, rb_intern("class"), 0) == rb_cFailedV8Conversion) {
|
928
|
+
// TODO try to recover stack trace from the conversion error
|
929
|
+
rb_raise(rb_eScriptRuntimeError, "Error converting JS object to Ruby object");
|
930
|
+
}
|
931
|
+
|
932
|
+
|
933
|
+
return ret;
|
934
|
+
}
|
935
|
+
|
936
|
+
static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) {
|
937
|
+
|
938
|
+
EvalParams eval_params;
|
939
|
+
EvalResult eval_result;
|
940
|
+
ContextInfo* context_info;
|
941
|
+
|
942
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
943
|
+
Isolate* isolate = context_info->isolate_info->isolate;
|
944
|
+
|
945
|
+
if(TYPE(str) != T_STRING) {
|
946
|
+
rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)",
|
947
|
+
rb_obj_class(str));
|
948
|
+
}
|
949
|
+
if(filename != Qnil && TYPE(filename) != T_STRING) {
|
950
|
+
rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be nil or a string)",
|
951
|
+
rb_obj_class(filename));
|
952
|
+
}
|
953
|
+
|
954
|
+
{
|
955
|
+
Locker lock(isolate);
|
956
|
+
Isolate::Scope isolate_scope(isolate);
|
957
|
+
HandleScope handle_scope(isolate);
|
958
|
+
|
959
|
+
Local<String> eval = String::NewFromUtf8(isolate, RSTRING_PTR(str),
|
960
|
+
NewStringType::kNormal, (int)RSTRING_LEN(str)).ToLocalChecked();
|
961
|
+
|
962
|
+
Local<String> local_filename;
|
963
|
+
|
964
|
+
if (filename != Qnil) {
|
965
|
+
local_filename = String::NewFromUtf8(isolate, RSTRING_PTR(filename),
|
966
|
+
NewStringType::kNormal, (int)RSTRING_LEN(filename)).ToLocalChecked();
|
967
|
+
eval_params.filename = &local_filename;
|
968
|
+
} else {
|
969
|
+
eval_params.filename = NULL;
|
970
|
+
}
|
971
|
+
|
972
|
+
eval_params.context_info = context_info;
|
973
|
+
eval_params.eval = &eval;
|
974
|
+
eval_params.result = &eval_result;
|
975
|
+
eval_params.timeout = 0;
|
976
|
+
eval_params.max_memory = 0;
|
977
|
+
VALUE timeout = rb_iv_get(self, "@timeout");
|
978
|
+
if (timeout != Qnil) {
|
979
|
+
eval_params.timeout = (useconds_t)NUM2LONG(timeout);
|
980
|
+
}
|
981
|
+
|
982
|
+
VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
|
983
|
+
if (mem_softlimit != Qnil) {
|
984
|
+
eval_params.max_memory = (size_t)NUM2ULONG(mem_softlimit);
|
985
|
+
}
|
986
|
+
|
987
|
+
eval_result.message = NULL;
|
988
|
+
eval_result.backtrace = NULL;
|
989
|
+
|
990
|
+
rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params);
|
991
|
+
}
|
992
|
+
|
993
|
+
return convert_result_to_ruby(self, eval_result);
|
994
|
+
}
|
995
|
+
|
996
|
+
typedef struct {
|
997
|
+
VALUE callback;
|
998
|
+
int length;
|
999
|
+
VALUE ruby_args;
|
1000
|
+
bool failed;
|
1001
|
+
} protected_callback_data;
|
1002
|
+
|
1003
|
+
static VALUE protected_callback(VALUE rdata) {
|
1004
|
+
protected_callback_data* data = (protected_callback_data*)rdata;
|
1005
|
+
VALUE result;
|
1006
|
+
|
1007
|
+
if (data->length > 0) {
|
1008
|
+
result = rb_funcall2(data->callback, rb_intern("call"), data->length,
|
1009
|
+
RARRAY_PTR(data->ruby_args));
|
1010
|
+
RB_GC_GUARD(data->ruby_args);
|
1011
|
+
} else {
|
1012
|
+
result = rb_funcall(data->callback, rb_intern("call"), 0);
|
1013
|
+
}
|
1014
|
+
return result;
|
1015
|
+
}
|
1016
|
+
|
1017
|
+
static
|
1018
|
+
VALUE rescue_callback(VALUE rdata, VALUE exception) {
|
1019
|
+
protected_callback_data* data = (protected_callback_data*)rdata;
|
1020
|
+
data->failed = true;
|
1021
|
+
return exception;
|
1022
|
+
}
|
1023
|
+
|
1024
|
+
void*
|
1025
|
+
gvl_ruby_callback(void* data) {
|
1026
|
+
|
1027
|
+
FunctionCallbackInfo<Value>* args = (FunctionCallbackInfo<Value>*)data;
|
1028
|
+
VALUE ruby_args = Qnil;
|
1029
|
+
int length = args->Length();
|
1030
|
+
VALUE callback;
|
1031
|
+
VALUE result;
|
1032
|
+
VALUE self;
|
1033
|
+
VALUE parent;
|
1034
|
+
ContextInfo* context_info;
|
1035
|
+
|
1036
|
+
{
|
1037
|
+
HandleScope scope(args->GetIsolate());
|
1038
|
+
Local<External> external = Local<External>::Cast(args->Data());
|
1039
|
+
|
1040
|
+
self = *(VALUE*)(external->Value());
|
1041
|
+
callback = rb_iv_get(self, "@callback");
|
1042
|
+
|
1043
|
+
parent = rb_iv_get(self, "@parent");
|
1044
|
+
if (NIL_P(parent) || !rb_obj_is_kind_of(parent, rb_cContext)) {
|
1045
|
+
return NULL;
|
1046
|
+
}
|
1047
|
+
|
1048
|
+
Data_Get_Struct(parent, ContextInfo, context_info);
|
1049
|
+
|
1050
|
+
if (length > 0) {
|
1051
|
+
ruby_args = rb_ary_tmp_new(length);
|
1052
|
+
}
|
1053
|
+
|
1054
|
+
for (int i = 0; i < length; i++) {
|
1055
|
+
Local<Value> value = ((*args)[i]).As<Value>();
|
1056
|
+
VALUE tmp = convert_v8_to_ruby(args->GetIsolate(),
|
1057
|
+
*context_info->context, value);
|
1058
|
+
rb_ary_push(ruby_args, tmp);
|
1059
|
+
}
|
1060
|
+
}
|
1061
|
+
|
1062
|
+
// may raise exception stay clear of handle scope
|
1063
|
+
protected_callback_data callback_data;
|
1064
|
+
callback_data.length = length;
|
1065
|
+
callback_data.callback = callback;
|
1066
|
+
callback_data.ruby_args = ruby_args;
|
1067
|
+
callback_data.failed = false;
|
1068
|
+
|
1069
|
+
if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
|
1070
|
+
args->GetIsolate()->ThrowException(
|
1071
|
+
String::NewFromUtf8Literal(args->GetIsolate(),
|
1072
|
+
"Terminated execution during transition from Ruby to JS"));
|
1073
|
+
args->GetIsolate()->TerminateExecution();
|
1074
|
+
if (length > 0) {
|
1075
|
+
rb_ary_clear(ruby_args);
|
1076
|
+
rb_gc_force_recycle(ruby_args);
|
1077
|
+
}
|
1078
|
+
return NULL;
|
1079
|
+
}
|
1080
|
+
|
1081
|
+
result = rb_rescue2((VALUE(*)(...))&protected_callback, (VALUE)(&callback_data),
|
1082
|
+
(VALUE(*)(...))&rescue_callback, (VALUE)(&callback_data), rb_eException, (VALUE)0);
|
1083
|
+
|
1084
|
+
if(callback_data.failed) {
|
1085
|
+
rb_iv_set(parent, "@current_exception", result);
|
1086
|
+
args->GetIsolate()->ThrowException(String::NewFromUtf8Literal(args->GetIsolate(), "Ruby exception"));
|
1087
|
+
}
|
1088
|
+
else {
|
1089
|
+
HandleScope scope(args->GetIsolate());
|
1090
|
+
Handle<Value> v8_result = convert_ruby_to_v8(args->GetIsolate(), context_info->context->Get(args->GetIsolate()), result);
|
1091
|
+
args->GetReturnValue().Set(v8_result);
|
1092
|
+
}
|
1093
|
+
|
1094
|
+
if (length > 0) {
|
1095
|
+
rb_ary_clear(ruby_args);
|
1096
|
+
rb_gc_force_recycle(ruby_args);
|
1097
|
+
}
|
1098
|
+
|
1099
|
+
if ((bool)args->GetIsolate()->GetData(DO_TERMINATE) == true) {
|
1100
|
+
Isolate* isolate = args->GetIsolate();
|
1101
|
+
isolate->TerminateExecution();
|
1102
|
+
}
|
1103
|
+
|
1104
|
+
return NULL;
|
1105
|
+
}
|
1106
|
+
|
1107
|
+
static void ruby_callback(const FunctionCallbackInfo<Value>& args) {
|
1108
|
+
bool has_gvl = (bool)args.GetIsolate()->GetData(IN_GVL);
|
1109
|
+
|
1110
|
+
if(has_gvl) {
|
1111
|
+
gvl_ruby_callback((void*)&args);
|
1112
|
+
} else {
|
1113
|
+
args.GetIsolate()->SetData(IN_GVL, (void*)true);
|
1114
|
+
rb_thread_call_with_gvl(gvl_ruby_callback, (void*)(&args));
|
1115
|
+
args.GetIsolate()->SetData(IN_GVL, (void*)false);
|
1116
|
+
}
|
1117
|
+
}
|
1118
|
+
|
1119
|
+
|
1120
|
+
static VALUE rb_external_function_notify_v8(VALUE self) {
|
1121
|
+
|
1122
|
+
ContextInfo* context_info;
|
1123
|
+
|
1124
|
+
VALUE parent = rb_iv_get(self, "@parent");
|
1125
|
+
VALUE name = rb_iv_get(self, "@name");
|
1126
|
+
VALUE parent_object = rb_iv_get(self, "@parent_object");
|
1127
|
+
VALUE parent_object_eval = rb_iv_get(self, "@parent_object_eval");
|
1128
|
+
|
1129
|
+
bool parse_error = false;
|
1130
|
+
bool attach_error = false;
|
1131
|
+
|
1132
|
+
Data_Get_Struct(parent, ContextInfo, context_info);
|
1133
|
+
Isolate* isolate = context_info->isolate_info->isolate;
|
1134
|
+
|
1135
|
+
{
|
1136
|
+
Locker lock(isolate);
|
1137
|
+
Isolate::Scope isolate_scope(isolate);
|
1138
|
+
HandleScope handle_scope(isolate);
|
1139
|
+
|
1140
|
+
Local<Context> context = context_info->context->Get(isolate);
|
1141
|
+
Context::Scope context_scope(context);
|
1142
|
+
|
1143
|
+
Local<String> v8_str =
|
1144
|
+
String::NewFromUtf8(isolate, RSTRING_PTR(name),
|
1145
|
+
NewStringType::kNormal, (int)RSTRING_LEN(name))
|
1146
|
+
.ToLocalChecked();
|
1147
|
+
|
1148
|
+
// copy self so we can access from v8 external
|
1149
|
+
VALUE* self_copy;
|
1150
|
+
Data_Get_Struct(self, VALUE, self_copy);
|
1151
|
+
*self_copy = self;
|
1152
|
+
|
1153
|
+
Local<Value> external = External::New(isolate, self_copy);
|
1154
|
+
|
1155
|
+
if (parent_object == Qnil) {
|
1156
|
+
context->Global()->Set(
|
1157
|
+
context,
|
1158
|
+
v8_str,
|
1159
|
+
FunctionTemplate::New(isolate, ruby_callback, external)
|
1160
|
+
->GetFunction(context)
|
1161
|
+
.ToLocalChecked());
|
1162
|
+
|
1163
|
+
} else {
|
1164
|
+
Local<String> eval =
|
1165
|
+
String::NewFromUtf8(isolate, RSTRING_PTR(parent_object_eval),
|
1166
|
+
NewStringType::kNormal,
|
1167
|
+
(int)RSTRING_LEN(parent_object_eval))
|
1168
|
+
.ToLocalChecked();
|
1169
|
+
|
1170
|
+
MaybeLocal<Script> parsed_script = Script::Compile(context, eval);
|
1171
|
+
if (parsed_script.IsEmpty()) {
|
1172
|
+
parse_error = true;
|
1173
|
+
} else {
|
1174
|
+
MaybeLocal<Value> maybe_value =
|
1175
|
+
parsed_script.ToLocalChecked()->Run(context);
|
1176
|
+
attach_error = true;
|
1177
|
+
|
1178
|
+
if (!maybe_value.IsEmpty()) {
|
1179
|
+
Local<Value> value = maybe_value.ToLocalChecked();
|
1180
|
+
if (value->IsObject()) {
|
1181
|
+
value.As<Object>()->Set(
|
1182
|
+
context,
|
1183
|
+
v8_str,
|
1184
|
+
FunctionTemplate::New(isolate, ruby_callback, external)
|
1185
|
+
->GetFunction(context)
|
1186
|
+
.ToLocalChecked());
|
1187
|
+
attach_error = false;
|
1188
|
+
}
|
1189
|
+
}
|
1190
|
+
}
|
1191
|
+
}
|
1192
|
+
}
|
1193
|
+
|
1194
|
+
// always raise out of V8 context
|
1195
|
+
if (parse_error) {
|
1196
|
+
rb_raise(rb_eParseError, "Invalid object %" PRIsVALUE, parent_object);
|
1197
|
+
}
|
1198
|
+
|
1199
|
+
if (attach_error) {
|
1200
|
+
rb_raise(rb_eParseError, "Was expecting %" PRIsVALUE" to be an object", parent_object);
|
1201
|
+
}
|
1202
|
+
|
1203
|
+
return Qnil;
|
1204
|
+
}
|
1205
|
+
|
1206
|
+
static VALUE rb_context_isolate_mutex(VALUE self) {
|
1207
|
+
ContextInfo* context_info;
|
1208
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
1209
|
+
|
1210
|
+
if (!context_info->isolate_info) {
|
1211
|
+
rb_raise(rb_eScriptRuntimeError, "Context has no Isolate available anymore");
|
1212
|
+
}
|
1213
|
+
|
1214
|
+
return context_info->isolate_info->mutex;
|
1215
|
+
}
|
1216
|
+
|
1217
|
+
IsolateInfo::~IsolateInfo() {
|
1218
|
+
if (isolate) {
|
1219
|
+
if (this->interrupted) {
|
1220
|
+
fprintf(stderr, "WARNING: V8 isolate was interrupted by Ruby, "
|
1221
|
+
"it can not be disposed and memory will not be "
|
1222
|
+
"reclaimed till the Ruby process exits.\n");
|
1223
|
+
} else {
|
1224
|
+
if (this->pid != getpid()) {
|
1225
|
+
fprintf(stderr, "WARNING: V8 isolate was forked, "
|
1226
|
+
"it can not be disposed and "
|
1227
|
+
"memory will not be reclaimed "
|
1228
|
+
"till the Ruby process exits.\n");
|
1229
|
+
} else {
|
1230
|
+
isolate->Dispose();
|
1231
|
+
}
|
1232
|
+
}
|
1233
|
+
isolate = nullptr;
|
1234
|
+
}
|
1235
|
+
|
1236
|
+
if (startup_data) {
|
1237
|
+
delete[] startup_data->data;
|
1238
|
+
delete startup_data;
|
1239
|
+
}
|
1240
|
+
|
1241
|
+
delete allocator;
|
1242
|
+
}
|
1243
|
+
|
1244
|
+
static void free_context_raw(void *arg) {
|
1245
|
+
ContextInfo* context_info = (ContextInfo*)arg;
|
1246
|
+
IsolateInfo* isolate_info = context_info->isolate_info;
|
1247
|
+
Persistent<Context>* context = context_info->context;
|
1248
|
+
|
1249
|
+
if (context && isolate_info && isolate_info->isolate) {
|
1250
|
+
Locker lock(isolate_info->isolate);
|
1251
|
+
v8::Isolate::Scope isolate_scope(isolate_info->isolate);
|
1252
|
+
context->Reset();
|
1253
|
+
delete context;
|
1254
|
+
}
|
1255
|
+
|
1256
|
+
if (isolate_info) {
|
1257
|
+
isolate_info->release();
|
1258
|
+
}
|
1259
|
+
|
1260
|
+
xfree(context_info);
|
1261
|
+
}
|
1262
|
+
|
1263
|
+
static void *free_context_thr(void* arg) {
|
1264
|
+
if (pthread_rwlock_tryrdlock(&exit_lock) != 0) {
|
1265
|
+
return NULL;
|
1266
|
+
}
|
1267
|
+
if (ruby_exiting) {
|
1268
|
+
return NULL;
|
1269
|
+
}
|
1270
|
+
|
1271
|
+
free_context_raw(arg);
|
1272
|
+
|
1273
|
+
pthread_rwlock_unlock(&exit_lock);
|
1274
|
+
|
1275
|
+
return NULL;
|
1276
|
+
}
|
1277
|
+
|
1278
|
+
// destroys everything except freeing the ContextInfo struct (see deallocate())
|
1279
|
+
static void free_context(ContextInfo* context_info) {
|
1280
|
+
|
1281
|
+
IsolateInfo* isolate_info = context_info->isolate_info;
|
1282
|
+
|
1283
|
+
ContextInfo* context_info_copy = ALLOC(ContextInfo);
|
1284
|
+
context_info_copy->isolate_info = context_info->isolate_info;
|
1285
|
+
context_info_copy->context = context_info->context;
|
1286
|
+
|
1287
|
+
if (isolate_info && isolate_info->refs() > 1) {
|
1288
|
+
pthread_t free_context_thread;
|
1289
|
+
if (pthread_create(&free_context_thread, thread_attr_p,
|
1290
|
+
free_context_thr, (void*)context_info_copy)) {
|
1291
|
+
fprintf(stderr, "WARNING failed to release memory in MiniRacer, thread to release could not be created, process will leak memory\n");
|
1292
|
+
}
|
1293
|
+
} else {
|
1294
|
+
free_context_raw(context_info_copy);
|
1295
|
+
}
|
1296
|
+
|
1297
|
+
context_info->context = NULL;
|
1298
|
+
context_info->isolate_info = NULL;
|
1299
|
+
}
|
1300
|
+
|
1301
|
+
static void deallocate_isolate(void* data) {
|
1302
|
+
|
1303
|
+
IsolateInfo* isolate_info = (IsolateInfo*) data;
|
1304
|
+
|
1305
|
+
isolate_info->release();
|
1306
|
+
}
|
1307
|
+
|
1308
|
+
static void mark_isolate(void* data) {
|
1309
|
+
IsolateInfo* isolate_info = (IsolateInfo*) data;
|
1310
|
+
isolate_info->mark();
|
1311
|
+
}
|
1312
|
+
|
1313
|
+
static void deallocate(void* data) {
|
1314
|
+
ContextInfo* context_info = (ContextInfo*)data;
|
1315
|
+
|
1316
|
+
free_context(context_info);
|
1317
|
+
|
1318
|
+
xfree(data);
|
1319
|
+
}
|
1320
|
+
|
1321
|
+
static void mark_context(void* data) {
|
1322
|
+
ContextInfo* context_info = (ContextInfo*)data;
|
1323
|
+
if (context_info->isolate_info) {
|
1324
|
+
context_info->isolate_info->mark();
|
1325
|
+
}
|
1326
|
+
}
|
1327
|
+
|
1328
|
+
static void deallocate_external_function(void * data) {
|
1329
|
+
xfree(data);
|
1330
|
+
}
|
1331
|
+
|
1332
|
+
static void deallocate_snapshot(void * data) {
|
1333
|
+
SnapshotInfo* snapshot_info = (SnapshotInfo*)data;
|
1334
|
+
delete[] snapshot_info->data;
|
1335
|
+
xfree(snapshot_info);
|
1336
|
+
}
|
1337
|
+
|
1338
|
+
static VALUE allocate_external_function(VALUE klass) {
|
1339
|
+
VALUE* self = ALLOC(VALUE);
|
1340
|
+
return Data_Wrap_Struct(klass, NULL, deallocate_external_function, (void*)self);
|
1341
|
+
}
|
1342
|
+
|
1343
|
+
static VALUE allocate(VALUE klass) {
|
1344
|
+
ContextInfo* context_info = ALLOC(ContextInfo);
|
1345
|
+
context_info->isolate_info = NULL;
|
1346
|
+
context_info->context = NULL;
|
1347
|
+
|
1348
|
+
return Data_Wrap_Struct(klass, mark_context, deallocate, (void*)context_info);
|
1349
|
+
}
|
1350
|
+
|
1351
|
+
static VALUE allocate_snapshot(VALUE klass) {
|
1352
|
+
SnapshotInfo* snapshot_info = ALLOC(SnapshotInfo);
|
1353
|
+
snapshot_info->data = NULL;
|
1354
|
+
snapshot_info->raw_size = 0;
|
1355
|
+
|
1356
|
+
return Data_Wrap_Struct(klass, NULL, deallocate_snapshot, (void*)snapshot_info);
|
1357
|
+
}
|
1358
|
+
|
1359
|
+
static VALUE allocate_isolate(VALUE klass) {
|
1360
|
+
IsolateInfo* isolate_info = new IsolateInfo();
|
1361
|
+
|
1362
|
+
return Data_Wrap_Struct(klass, mark_isolate, deallocate_isolate, (void*)isolate_info);
|
1363
|
+
}
|
1364
|
+
|
1365
|
+
static VALUE
|
1366
|
+
rb_heap_stats(VALUE self) {
|
1367
|
+
|
1368
|
+
ContextInfo* context_info;
|
1369
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
1370
|
+
Isolate* isolate;
|
1371
|
+
v8::HeapStatistics stats;
|
1372
|
+
|
1373
|
+
isolate = context_info->isolate_info ? context_info->isolate_info->isolate : NULL;
|
1374
|
+
|
1375
|
+
VALUE rval = rb_hash_new();
|
1376
|
+
|
1377
|
+
if (!isolate) {
|
1378
|
+
|
1379
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(0));
|
1380
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(0));
|
1381
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(0));
|
1382
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(0));
|
1383
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(0));
|
1384
|
+
|
1385
|
+
} else {
|
1386
|
+
isolate->GetHeapStatistics(&stats);
|
1387
|
+
|
1388
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("total_physical_size")), ULONG2NUM(stats.total_physical_size()));
|
1389
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size_executable")), ULONG2NUM(stats.total_heap_size_executable()));
|
1390
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("total_heap_size")), ULONG2NUM(stats.total_heap_size()));
|
1391
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("used_heap_size")), ULONG2NUM(stats.used_heap_size()));
|
1392
|
+
rb_hash_aset(rval, ID2SYM(rb_intern("heap_size_limit")), ULONG2NUM(stats.heap_size_limit()));
|
1393
|
+
}
|
1394
|
+
|
1395
|
+
return rval;
|
1396
|
+
}
|
1397
|
+
|
1398
|
+
// https://github.com/bnoordhuis/node-heapdump/blob/master/src/heapdump.cc
|
1399
|
+
class FileOutputStream : public OutputStream {
|
1400
|
+
public:
|
1401
|
+
FileOutputStream(FILE* stream) : stream_(stream) {}
|
1402
|
+
|
1403
|
+
virtual int GetChunkSize() {
|
1404
|
+
return 65536;
|
1405
|
+
}
|
1406
|
+
|
1407
|
+
virtual void EndOfStream() {}
|
1408
|
+
|
1409
|
+
virtual WriteResult WriteAsciiChunk(char* data, int size) {
|
1410
|
+
const size_t len = static_cast<size_t>(size);
|
1411
|
+
size_t off = 0;
|
1412
|
+
|
1413
|
+
while (off < len && !feof(stream_) && !ferror(stream_))
|
1414
|
+
off += fwrite(data + off, 1, len - off, stream_);
|
1415
|
+
|
1416
|
+
return off == len ? kContinue : kAbort;
|
1417
|
+
}
|
1418
|
+
|
1419
|
+
private:
|
1420
|
+
FILE* stream_;
|
1421
|
+
};
|
1422
|
+
|
1423
|
+
|
1424
|
+
static VALUE
|
1425
|
+
rb_heap_snapshot(VALUE self, VALUE file) {
|
1426
|
+
|
1427
|
+
rb_io_t *fptr;
|
1428
|
+
|
1429
|
+
fptr = RFILE(file)->fptr;
|
1430
|
+
|
1431
|
+
if (!fptr) return Qfalse;
|
1432
|
+
|
1433
|
+
FILE* fp;
|
1434
|
+
fp = fdopen(fptr->fd, "w");
|
1435
|
+
if (fp == NULL) return Qfalse;
|
1436
|
+
|
1437
|
+
|
1438
|
+
ContextInfo* context_info;
|
1439
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
1440
|
+
Isolate* isolate;
|
1441
|
+
isolate = context_info->isolate_info ? context_info->isolate_info->isolate : NULL;
|
1442
|
+
|
1443
|
+
if (!isolate) return Qfalse;
|
1444
|
+
|
1445
|
+
Locker lock(isolate);
|
1446
|
+
Isolate::Scope isolate_scope(isolate);
|
1447
|
+
HandleScope handle_scope(isolate);
|
1448
|
+
|
1449
|
+
HeapProfiler* heap_profiler = isolate->GetHeapProfiler();
|
1450
|
+
|
1451
|
+
const HeapSnapshot* const snap = heap_profiler->TakeHeapSnapshot();
|
1452
|
+
|
1453
|
+
FileOutputStream stream(fp);
|
1454
|
+
snap->Serialize(&stream, HeapSnapshot::kJSON);
|
1455
|
+
|
1456
|
+
fflush(fp);
|
1457
|
+
|
1458
|
+
const_cast<HeapSnapshot*>(snap)->Delete();
|
1459
|
+
|
1460
|
+
return Qtrue;
|
1461
|
+
}
|
1462
|
+
|
1463
|
+
static VALUE
|
1464
|
+
rb_context_stop(VALUE self) {
|
1465
|
+
|
1466
|
+
ContextInfo* context_info;
|
1467
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
1468
|
+
|
1469
|
+
Isolate* isolate = context_info->isolate_info->isolate;
|
1470
|
+
|
1471
|
+
// flag for termination
|
1472
|
+
isolate->SetData(DO_TERMINATE, (void*)true);
|
1473
|
+
|
1474
|
+
isolate->TerminateExecution();
|
1475
|
+
rb_funcall(self, rb_intern("stop_attached"), 0);
|
1476
|
+
|
1477
|
+
return Qnil;
|
1478
|
+
}
|
1479
|
+
|
1480
|
+
static VALUE
|
1481
|
+
rb_context_dispose(VALUE self) {
|
1482
|
+
|
1483
|
+
ContextInfo* context_info;
|
1484
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
1485
|
+
|
1486
|
+
free_context(context_info);
|
1487
|
+
|
1488
|
+
return Qnil;
|
1489
|
+
}
|
1490
|
+
|
1491
|
+
static void*
|
1492
|
+
nogvl_context_call(void *args) {
|
1493
|
+
|
1494
|
+
FunctionCall *call = (FunctionCall *) args;
|
1495
|
+
if (!call) {
|
1496
|
+
return NULL;
|
1497
|
+
}
|
1498
|
+
IsolateInfo *isolate_info = call->context_info->isolate_info;
|
1499
|
+
Isolate* isolate = isolate_info->isolate;
|
1500
|
+
|
1501
|
+
// in gvl flag
|
1502
|
+
isolate->SetData(IN_GVL, (void*)false);
|
1503
|
+
// terminate ASAP
|
1504
|
+
isolate->SetData(DO_TERMINATE, (void*)false);
|
1505
|
+
|
1506
|
+
if (call->max_memory > 0) {
|
1507
|
+
isolate->SetData(MEM_SOFTLIMIT_VALUE, &call->max_memory);
|
1508
|
+
isolate->SetData(MEM_SOFTLIMIT_REACHED, (void*)false);
|
1509
|
+
if (!isolate_info->added_gc_cb) {
|
1510
|
+
isolate->AddGCEpilogueCallback(gc_callback);
|
1511
|
+
isolate_info->added_gc_cb = true;
|
1512
|
+
}
|
1513
|
+
}
|
1514
|
+
|
1515
|
+
Isolate::Scope isolate_scope(isolate);
|
1516
|
+
EscapableHandleScope handle_scope(isolate);
|
1517
|
+
TryCatch trycatch(isolate);
|
1518
|
+
|
1519
|
+
Local<Context> context = call->context_info->context->Get(isolate);
|
1520
|
+
Context::Scope context_scope(context);
|
1521
|
+
|
1522
|
+
Local<Function> fun = call->fun;
|
1523
|
+
|
1524
|
+
EvalResult& eval_res = call->result;
|
1525
|
+
eval_res.parsed = true;
|
1526
|
+
|
1527
|
+
MaybeLocal<v8::Value> res = fun->Call(context, context->Global(), call->argc, call->argv);
|
1528
|
+
prepare_result(res, trycatch, isolate, context, eval_res);
|
1529
|
+
|
1530
|
+
isolate->SetData(IN_GVL, (void*)true);
|
1531
|
+
|
1532
|
+
return NULL;
|
1533
|
+
}
|
1534
|
+
|
1535
|
+
static void unblock_function(void *args) {
|
1536
|
+
FunctionCall *call = (FunctionCall *) args;
|
1537
|
+
call->context_info->isolate_info->interrupted = true;
|
1538
|
+
}
|
1539
|
+
|
1540
|
+
static VALUE rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) {
|
1541
|
+
ContextInfo* context_info;
|
1542
|
+
FunctionCall call;
|
1543
|
+
VALUE *call_argv = NULL;
|
1544
|
+
|
1545
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
1546
|
+
Isolate* isolate = context_info->isolate_info->isolate;
|
1547
|
+
|
1548
|
+
if (argc < 1) {
|
1549
|
+
rb_raise(rb_eArgError, "need at least one argument %d", argc);
|
1550
|
+
}
|
1551
|
+
|
1552
|
+
VALUE function_name = argv[0];
|
1553
|
+
if (TYPE(function_name) != T_STRING) {
|
1554
|
+
rb_raise(rb_eTypeError, "first argument should be a String");
|
1555
|
+
}
|
1556
|
+
|
1557
|
+
char *fname = RSTRING_PTR(function_name);
|
1558
|
+
if (!fname) {
|
1559
|
+
return Qnil;
|
1560
|
+
}
|
1561
|
+
|
1562
|
+
call.context_info = context_info;
|
1563
|
+
call.error = false;
|
1564
|
+
call.function_name = fname;
|
1565
|
+
call.argc = argc - 1;
|
1566
|
+
call.argv = NULL;
|
1567
|
+
if (call.argc > 0) {
|
1568
|
+
// skip first argument which is the function name
|
1569
|
+
call_argv = argv + 1;
|
1570
|
+
}
|
1571
|
+
|
1572
|
+
call.max_memory = 0;
|
1573
|
+
VALUE mem_softlimit = rb_iv_get(self, "@max_memory");
|
1574
|
+
if (mem_softlimit != Qnil) {
|
1575
|
+
unsigned long sl_int = NUM2ULONG(mem_softlimit);
|
1576
|
+
call.max_memory = (size_t)sl_int;
|
1577
|
+
}
|
1578
|
+
|
1579
|
+
bool missingFunction = false;
|
1580
|
+
{
|
1581
|
+
Locker lock(isolate);
|
1582
|
+
Isolate::Scope isolate_scope(isolate);
|
1583
|
+
HandleScope handle_scope(isolate);
|
1584
|
+
|
1585
|
+
Local<Context> context = context_info->context->Get(isolate);
|
1586
|
+
Context::Scope context_scope(context);
|
1587
|
+
|
1588
|
+
// examples of such usage can be found in
|
1589
|
+
// https://github.com/v8/v8/blob/36b32aa28db5e993312f4588d60aad5c8330c8a5/test/cctest/test-api.cc#L15711
|
1590
|
+
MaybeLocal<String> fname = String::NewFromUtf8(isolate, call.function_name);
|
1591
|
+
MaybeLocal<v8::Value> val;
|
1592
|
+
if (!fname.IsEmpty()) {
|
1593
|
+
val = context->Global()->Get(context, fname.ToLocalChecked());
|
1594
|
+
}
|
1595
|
+
|
1596
|
+
if (val.IsEmpty() || !val.ToLocalChecked()->IsFunction()) {
|
1597
|
+
missingFunction = true;
|
1598
|
+
} else {
|
1599
|
+
|
1600
|
+
Local<v8::Function> fun = Local<v8::Function>::Cast(val.ToLocalChecked());
|
1601
|
+
call.fun = fun;
|
1602
|
+
int fun_argc = call.argc;
|
1603
|
+
|
1604
|
+
if (fun_argc > 0) {
|
1605
|
+
call.argv = (v8::Local<Value> *) malloc(sizeof(void *) * fun_argc);
|
1606
|
+
if (!call.argv) {
|
1607
|
+
return Qnil;
|
1608
|
+
}
|
1609
|
+
for(int i=0; i < fun_argc; i++) {
|
1610
|
+
call.argv[i] = convert_ruby_to_v8(isolate, context, call_argv[i]);
|
1611
|
+
}
|
1612
|
+
}
|
1613
|
+
rb_thread_call_without_gvl(nogvl_context_call, &call, unblock_function, &call);
|
1614
|
+
free(call.argv);
|
1615
|
+
|
1616
|
+
}
|
1617
|
+
}
|
1618
|
+
|
1619
|
+
if (missingFunction) {
|
1620
|
+
rb_raise(rb_eScriptRuntimeError, "Unknown JavaScript method invoked");
|
1621
|
+
}
|
1622
|
+
|
1623
|
+
return convert_result_to_ruby(self, call.result);
|
1624
|
+
}
|
1625
|
+
|
1626
|
+
static VALUE rb_context_create_isolate_value(VALUE self) {
|
1627
|
+
ContextInfo* context_info;
|
1628
|
+
Data_Get_Struct(self, ContextInfo, context_info);
|
1629
|
+
IsolateInfo *isolate_info = context_info->isolate_info;
|
1630
|
+
|
1631
|
+
if (!isolate_info) {
|
1632
|
+
return Qnil;
|
1633
|
+
}
|
1634
|
+
|
1635
|
+
isolate_info->hold();
|
1636
|
+
return Data_Wrap_Struct(rb_cIsolate, NULL, &deallocate_isolate, isolate_info);
|
1637
|
+
}
|
1638
|
+
|
1639
|
+
static void set_ruby_exiting(VALUE value) {
|
1640
|
+
(void)value;
|
1641
|
+
|
1642
|
+
int res = pthread_rwlock_wrlock(&exit_lock);
|
1643
|
+
ruby_exiting = true;
|
1644
|
+
if (res == 0) {
|
1645
|
+
pthread_rwlock_unlock(&exit_lock);
|
1646
|
+
}
|
1647
|
+
}
|
1648
|
+
|
1649
|
+
extern "C" {
|
1650
|
+
|
1651
|
+
void Init_mini_racer_extension ( void )
|
1652
|
+
{
|
1653
|
+
VALUE rb_mMiniRacer = rb_define_module("MiniRacer");
|
1654
|
+
rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject);
|
1655
|
+
rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject);
|
1656
|
+
rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject);
|
1657
|
+
VALUE rb_cPlatform = rb_define_class_under(rb_mMiniRacer, "Platform", rb_cObject);
|
1658
|
+
|
1659
|
+
VALUE rb_eError = rb_define_class_under(rb_mMiniRacer, "Error", rb_eStandardError);
|
1660
|
+
|
1661
|
+
VALUE rb_eEvalError = rb_define_class_under(rb_mMiniRacer, "EvalError", rb_eError);
|
1662
|
+
rb_eScriptTerminatedError = rb_define_class_under(rb_mMiniRacer, "ScriptTerminatedError", rb_eEvalError);
|
1663
|
+
rb_eV8OutOfMemoryError = rb_define_class_under(rb_mMiniRacer, "V8OutOfMemoryError", rb_eEvalError);
|
1664
|
+
rb_eParseError = rb_define_class_under(rb_mMiniRacer, "ParseError", rb_eEvalError);
|
1665
|
+
rb_eScriptRuntimeError = rb_define_class_under(rb_mMiniRacer, "RuntimeError", rb_eEvalError);
|
1666
|
+
|
1667
|
+
rb_cJavaScriptFunction = rb_define_class_under(rb_mMiniRacer, "JavaScriptFunction", rb_cObject);
|
1668
|
+
rb_eSnapshotError = rb_define_class_under(rb_mMiniRacer, "SnapshotError", rb_eError);
|
1669
|
+
rb_ePlatformAlreadyInitializedError = rb_define_class_under(rb_mMiniRacer, "PlatformAlreadyInitialized", rb_eError);
|
1670
|
+
rb_cFailedV8Conversion = rb_define_class_under(rb_mMiniRacer, "FailedV8Conversion", rb_cObject);
|
1671
|
+
rb_mJSON = rb_define_module("JSON");
|
1672
|
+
|
1673
|
+
VALUE rb_cExternalFunction = rb_define_class_under(rb_cContext, "ExternalFunction", rb_cObject);
|
1674
|
+
|
1675
|
+
rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
|
1676
|
+
rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
|
1677
|
+
rb_define_method(rb_cContext, "heap_stats", (VALUE(*)(...))&rb_heap_stats, 0);
|
1678
|
+
rb_define_method(rb_cContext, "write_heap_snapshot_unsafe", (VALUE(*)(...))&rb_heap_snapshot, 1);
|
1679
|
+
|
1680
|
+
rb_define_private_method(rb_cContext, "create_isolate_value",(VALUE(*)(...))&rb_context_create_isolate_value, 0);
|
1681
|
+
rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
|
1682
|
+
rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
|
1683
|
+
rb_define_private_method(rb_cContext, "isolate_mutex", (VALUE(*)(...))&rb_context_isolate_mutex, 0);
|
1684
|
+
rb_define_private_method(rb_cContext, "init_unsafe",(VALUE(*)(...))&rb_context_init_unsafe, 2);
|
1685
|
+
|
1686
|
+
rb_define_alloc_func(rb_cContext, allocate);
|
1687
|
+
rb_define_alloc_func(rb_cSnapshot, allocate_snapshot);
|
1688
|
+
rb_define_alloc_func(rb_cIsolate, allocate_isolate);
|
1689
|
+
|
1690
|
+
rb_define_private_method(rb_cExternalFunction, "notify_v8", (VALUE(*)(...))&rb_external_function_notify_v8, 0);
|
1691
|
+
rb_define_alloc_func(rb_cExternalFunction, allocate_external_function);
|
1692
|
+
|
1693
|
+
rb_define_method(rb_cSnapshot, "size", (VALUE(*)(...))&rb_snapshot_size, 0);
|
1694
|
+
rb_define_method(rb_cSnapshot, "dump", (VALUE(*)(...))&rb_snapshot_dump, 0);
|
1695
|
+
rb_define_method(rb_cSnapshot, "warmup_unsafe!", (VALUE(*)(...))&rb_snapshot_warmup_unsafe, 1);
|
1696
|
+
rb_define_private_method(rb_cSnapshot, "load", (VALUE(*)(...))&rb_snapshot_load, 1);
|
1697
|
+
|
1698
|
+
rb_define_method(rb_cIsolate, "idle_notification", (VALUE(*)(...))&rb_isolate_idle_notification, 1);
|
1699
|
+
rb_define_method(rb_cIsolate, "low_memory_notification", (VALUE(*)(...))&rb_isolate_low_memory_notification, 0);
|
1700
|
+
rb_define_method(rb_cIsolate, "pump_message_loop", (VALUE(*)(...))&rb_isolate_pump_message_loop, 0);
|
1701
|
+
rb_define_private_method(rb_cIsolate, "init_with_snapshot",(VALUE(*)(...))&rb_isolate_init_with_snapshot, 1);
|
1702
|
+
|
1703
|
+
rb_define_singleton_method(rb_cPlatform, "set_flag_as_str!", (VALUE(*)(...))&rb_platform_set_flag_as_str, 1);
|
1704
|
+
|
1705
|
+
rb_set_end_proc(set_ruby_exiting, Qnil);
|
1706
|
+
|
1707
|
+
static pthread_attr_t attr;
|
1708
|
+
if (pthread_attr_init(&attr) == 0) {
|
1709
|
+
if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0) {
|
1710
|
+
thread_attr_p = &attr;
|
1711
|
+
}
|
1712
|
+
}
|
1713
|
+
}
|
1714
|
+
}
|