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