h8 0.1.4 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -10
- data/ext/h8/JsCatcher.cpp +5 -0
- data/ext/h8/h8.cpp +40 -2
- data/ext/h8/h8.h +13 -0
- data/ext/h8/js_gate.cpp +31 -4
- data/ext/h8/ruby_gate.cpp +71 -44
- data/ext/h8/ruby_gate.h +27 -13
- data/lib/h8/coffee.rb +1 -1
- data/lib/h8/version.rb +1 -1
- data/spec/context_spec.rb +55 -28
- data/spec/ruby_gate_spec.rb +4 -1
- data/spec/threading_spec.rb +46 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d9eec220ddcefcec439cacbc06a3a1aa9df74a4
|
4
|
+
data.tar.gz: ff56274f0e8f976ea5afe8b35ed01259032330db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 43eecac7e09f262bddbcb68fe49717c82f27778205bc2b6aa2bb0808b1ea6bc2131c7bdb3d808253a7a35134ff8da9c7cb5d92d190b86f6f46b5e39388782f1d
|
7
|
+
data.tar.gz: 9741491ec26a3698b9a431624c4726db9989afa6ece6e09773eec14a7dcc711ca1fd9163467cc27e27ce2a947daa3af74713f99e17edc6407793a956c965e0ac
|
data/README.md
CHANGED
@@ -8,7 +8,8 @@ This gem was intended to replace therubyracer for many reasons:
|
|
8
8
|
* therubyracer has critical bugs that are not fixed for a long time, under load it produces
|
9
9
|
numerous frequent crashes.
|
10
10
|
|
11
|
-
* therubyracer still uses antique version of V8, H8 uses the latest 3.31 branch
|
11
|
+
* therubyracer still uses antique version of V8, H8 uses the latest 3.31 branch, which, for example,
|
12
|
+
has the generators support.
|
12
13
|
|
13
14
|
* H8 is designed to provide very tight and effective integration of two allocation systems and
|
14
15
|
object models, passing the same objects between different systems wrapping and unwrapping them
|
@@ -36,11 +37,12 @@ uncaught javascript exceptions raise ruby error in ruby code.
|
|
36
37
|
|
37
38
|
- Integrated CoffeeScript support
|
38
39
|
|
39
|
-
|
40
|
+
- H8 is thread safe (using Lockers) and releases gvl when executing js code (and reqcquires it as
|
41
|
+
need), thus other ruby threads can work in parallel with javascript executing threads. Still,
|
42
|
+
h8 does not releases Locker when calling ruby code from javascript - for performance considerations.
|
43
|
+
|
40
44
|
|
41
|
-
|
42
|
-
releasing gvl (other ruby threads can not perform on this core while javascript code runs). We are
|
43
|
-
working on it, as simply releasing GVL and reacquring it may degrade performance.
|
45
|
+
## Main difference from therubyracer/features not ready
|
44
46
|
|
45
47
|
- labmda/proc passed as var to the context **does not receives first (this) argument
|
46
48
|
automatically!**
|
@@ -98,11 +100,7 @@ Install first a valid v8 version. We provide a ready package!
|
|
98
100
|
|
99
101
|
sudo apt-get install libv8-3.31-dev
|
100
102
|
|
101
|
-
|
102
|
-
|
103
|
-
sudo apt-get install libicu-dev
|
104
|
-
|
105
|
-
You might also need to install GMP.
|
103
|
+
Usually it is all you need. Rarely, You might also need to install GMP.
|
106
104
|
|
107
105
|
### Setting up
|
108
106
|
|
data/ext/h8/JsCatcher.cpp
CHANGED
@@ -14,7 +14,12 @@ JsCatcher::JsCatcher(H8* h8) : h8(h8), v8::TryCatch(h8->getIsolate()) {}
|
|
14
14
|
|
15
15
|
void JsCatcher::throwIfCaught() {
|
16
16
|
if( HasCaught() ) {
|
17
|
+
if( h8->isInterrupted() ) {
|
18
|
+
puts("INTERRUPTED!");
|
19
|
+
throw JsError(h8, "interrupted");
|
20
|
+
}
|
17
21
|
if( !CanContinue() && HasTerminated() ) {
|
22
|
+
// if( HasTerminated() ) {
|
18
23
|
throw JsTimeoutError(h8);
|
19
24
|
}
|
20
25
|
throw JsError(h8, Message(), Exception());
|
data/ext/h8/h8.cpp
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
#include <chrono>
|
5
5
|
|
6
6
|
#include "h8.h"
|
7
|
+
#include <ruby/thread.h>
|
7
8
|
#include "ruby_gate.h"
|
8
9
|
|
9
10
|
void h8::JsError::raise() {
|
@@ -61,6 +62,43 @@ void h8::H8::ruby_mark_gc() const {
|
|
61
62
|
((AllocatedResource*) x)->rb_mark_gc();
|
62
63
|
}
|
63
64
|
|
65
|
+
struct CallerParams {
|
66
|
+
h8::H8* h8;
|
67
|
+
Handle<Script>& script;
|
68
|
+
Local<Value>& result;
|
69
|
+
};
|
70
|
+
|
71
|
+
static void* script_caller(void* param) {
|
72
|
+
CallerParams *cp = (CallerParams*) param;
|
73
|
+
cp->h8->setGvlReleased(true);
|
74
|
+
cp->result = cp->script->Run();
|
75
|
+
return NULL;
|
76
|
+
}
|
77
|
+
|
78
|
+
static void unblock_script(void* param) {
|
79
|
+
h8::H8* h8 = (h8::H8*) param;
|
80
|
+
if( rb_thread_interrupted(rb_thread_current()) ) {
|
81
|
+
printf("UNBLOCK!!!! CALLED!!! Why? %p\n", param);
|
82
|
+
h8->setInterrupted();
|
83
|
+
h8->getIsolate()->TerminateExecution();
|
84
|
+
}
|
85
|
+
else {
|
86
|
+
puts("UBF not interrupted. why?");
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
void h8::H8::invoke(v8::Handle<v8::Script> script, Local<Value>& result) {
|
91
|
+
#if 1
|
92
|
+
CallerParams cp = { this, script, result };
|
93
|
+
rb_interrupted = false;
|
94
|
+
rb_thread_call_without_gvl(script_caller, &cp, NULL, NULL);
|
95
|
+
setGvlReleased(false);
|
96
|
+
#else
|
97
|
+
gvl_released = false;
|
98
|
+
result = script->Run();
|
99
|
+
#endif
|
100
|
+
}
|
101
|
+
|
64
102
|
v8::Handle<v8::Value> h8::H8::eval(const char* script_utf, unsigned max_ms) {
|
65
103
|
v8::EscapableHandleScope escape(isolate);
|
66
104
|
Local<Value> result;
|
@@ -87,11 +125,11 @@ v8::Handle<v8::Value> h8::H8::eval(const char* script_utf, unsigned max_ms) {
|
|
87
125
|
isolate->TerminateExecution();
|
88
126
|
}
|
89
127
|
});
|
90
|
-
script
|
128
|
+
invoke(script, result);
|
91
129
|
cv.notify_all();
|
92
130
|
thr.join();
|
93
131
|
} else {
|
94
|
-
result
|
132
|
+
invoke(script, result);
|
95
133
|
}
|
96
134
|
try_catch.throwIfCaught();
|
97
135
|
}
|
data/ext/h8/h8.h
CHANGED
@@ -197,10 +197,21 @@ public:
|
|
197
197
|
|
198
198
|
void ruby_mark_gc() const;
|
199
199
|
|
200
|
+
bool isGvlReleased() const noexcept { return gvl_released; }
|
201
|
+
|
202
|
+
void setGvlReleased(bool state) noexcept { gvl_released = state; }
|
203
|
+
|
204
|
+
void setInterrupted() {
|
205
|
+
rb_interrupted = true;
|
206
|
+
}
|
207
|
+
|
208
|
+
bool isInterrupted() const { return rb_interrupted; }
|
209
|
+
|
200
210
|
virtual ~H8();
|
201
211
|
|
202
212
|
private:
|
203
213
|
friend VALUE h8::context_alloc(VALUE klass);
|
214
|
+
void invoke(v8::Handle<v8::Script> script, Local<Value>& result);
|
204
215
|
|
205
216
|
Isolate *isolate;
|
206
217
|
VALUE self;
|
@@ -208,6 +219,8 @@ private:
|
|
208
219
|
Persistent<Context> persistent_context;
|
209
220
|
|
210
221
|
bool is_error = false;
|
222
|
+
bool gvl_released = false;
|
223
|
+
bool rb_interrupted = false;
|
211
224
|
|
212
225
|
chain resources;
|
213
226
|
};
|
data/ext/h8/js_gate.cpp
CHANGED
@@ -1,18 +1,45 @@
|
|
1
1
|
#include "h8.h"
|
2
2
|
#include "JsCatcher.h"
|
3
|
+
#include <ruby/thread.h>
|
3
4
|
|
4
5
|
using namespace h8;
|
5
6
|
|
7
|
+
struct ApplyParams {
|
8
|
+
Local<Object> object;
|
9
|
+
Local<Value> &result;
|
10
|
+
Local<Value>& self;
|
11
|
+
Local<Value> *js_args;
|
12
|
+
int count;
|
13
|
+
H8* h8;
|
14
|
+
};
|
15
|
+
|
16
|
+
static inline void* call_without_gvl(void* param) {
|
17
|
+
ApplyParams *ap = (ApplyParams*)param;
|
18
|
+
ap->h8->setGvlReleased(true);
|
19
|
+
ap->result = ap->object->CallAsFunction(ap->self, ap->count, ap->js_args);
|
20
|
+
return NULL;
|
21
|
+
}
|
22
|
+
|
23
|
+
#define MAX_ARGS (128)
|
24
|
+
|
6
25
|
VALUE JsGate::apply(Local<Value> self, VALUE args) const {
|
7
26
|
H8::Scope scope(h8);
|
8
|
-
|
9
|
-
|
27
|
+
int count = RARRAY_LEN(args);
|
28
|
+
if( count >= MAX_ARGS )
|
29
|
+
throw JsError(h8, "Too many arguments for the callable");
|
30
|
+
// Local<Value> *js_args = new Local<Value> [count];
|
31
|
+
Local<Value> js_args[MAX_ARGS];
|
10
32
|
for (int i = 0; i < count; i++) {
|
11
33
|
js_args[i] = h8->to_js(rb_ary_entry(args, i));
|
12
34
|
}
|
13
35
|
h8::JsCatcher catcher(h8);
|
14
|
-
Local<Value> result
|
36
|
+
Local<Value> result;
|
37
|
+
|
38
|
+
ApplyParams ap = { object(), result, self, js_args, count, h8 };
|
39
|
+
rb_thread_call_without_gvl(call_without_gvl, &ap, NULL, NULL );
|
40
|
+
h8->setGvlReleased(false);
|
41
|
+
|
15
42
|
catcher.throwIfCaught();
|
16
|
-
delete [] js_args;
|
43
|
+
// delete [] js_args;
|
17
44
|
return h8->to_ruby(result);
|
18
45
|
}
|
data/ext/h8/ruby_gate.cpp
CHANGED
@@ -1,9 +1,31 @@
|
|
1
|
+
#include <functional>
|
1
2
|
#include "h8.h"
|
2
3
|
#include "ruby_gate.h"
|
3
4
|
#include <ruby.h>
|
5
|
+
#include <ruby/thread.h>
|
4
6
|
|
5
7
|
using namespace h8;
|
6
8
|
|
9
|
+
static void* unblock_caller(void *param) {
|
10
|
+
const std::function<void(void)>* pblock =
|
11
|
+
(const std::function<void(void)>*) param;
|
12
|
+
(*pblock)();
|
13
|
+
return NULL;
|
14
|
+
}
|
15
|
+
|
16
|
+
static void with_gvl(RubyGate *gate,
|
17
|
+
const std::function<void(void)> &block) {
|
18
|
+
// v8::Unlocker unlock(gate->isolate());
|
19
|
+
H8 *h8 = gate->getH8();
|
20
|
+
if (h8->isGvlReleased()) {
|
21
|
+
h8->setGvlReleased(false);
|
22
|
+
rb_thread_call_with_gvl(unblock_caller, (void*) &block);
|
23
|
+
h8->setGvlReleased(true);
|
24
|
+
}
|
25
|
+
else
|
26
|
+
block();
|
27
|
+
}
|
28
|
+
|
7
29
|
h8::RubyGate::RubyGate(H8* _context, VALUE object) :
|
8
30
|
context(_context), ruby_object(object), next(0), prev(0) {
|
9
31
|
v8::HandleScope scope(context->getIsolate());
|
@@ -15,7 +37,7 @@ h8::RubyGate::RubyGate(H8* _context, VALUE object) :
|
|
15
37
|
templ->SetCallAsFunctionHandler(&ObjectCallback);
|
16
38
|
|
17
39
|
templ->SetNamedPropertyHandler(RubyGate::mapGet, RubyGate::mapSet);
|
18
|
-
templ->SetIndexedPropertyHandler(RubyGate::indexGet,RubyGate::indexSet);
|
40
|
+
templ->SetIndexedPropertyHandler(RubyGate::indexGet, RubyGate::indexSet);
|
19
41
|
|
20
42
|
v8::Handle<v8::Object> handle = templ->NewInstance();
|
21
43
|
handle->SetAlignedPointerInInternalField(1, RUBYGATE_ID);
|
@@ -29,8 +51,7 @@ void h8::RubyGate::mapGet(Local<String> name,
|
|
29
51
|
rg->getProperty(name, info);
|
30
52
|
}
|
31
53
|
|
32
|
-
void h8::RubyGate::mapSet(Local<String> name,
|
33
|
-
Local<Value> value,
|
54
|
+
void h8::RubyGate::mapSet(Local<String> name, Local<Value> value,
|
34
55
|
const PropertyCallbackInfo<Value> &info) {
|
35
56
|
RubyGate *rg = RubyGate::unwrap(info.This());
|
36
57
|
assert(rg != 0);
|
@@ -44,8 +65,7 @@ void h8::RubyGate::indexGet(uint32_t index,
|
|
44
65
|
rg->getIndex(index, info);
|
45
66
|
}
|
46
67
|
|
47
|
-
void h8::RubyGate::indexSet(uint32_t index,
|
48
|
-
Local<Value> value,
|
68
|
+
void h8::RubyGate::indexSet(uint32_t index, Local<Value> value,
|
49
69
|
const PropertyCallbackInfo<Value> &info) {
|
50
70
|
RubyGate *rg = RubyGate::unwrap(info.This());
|
51
71
|
assert(rg != 0);
|
@@ -99,59 +119,66 @@ void h8::RubyGate::rescued_call(VALUE rb_args, VALUE (*call)(VALUE),
|
|
99
119
|
|
100
120
|
void h8::RubyGate::doObjectCallback(
|
101
121
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
122
|
+
with_gvl(this, [&] {
|
123
|
+
VALUE rb_args = ruby_args(args, 1);
|
124
|
+
rb_ary_push(rb_args, ruby_object);
|
125
|
+
rescued_call(rb_args, call, [&] (VALUE res) {
|
126
|
+
args.GetReturnValue().Set(context->to_js(res));
|
127
|
+
});
|
107
128
|
});
|
108
129
|
}
|
109
130
|
|
110
131
|
void h8::RubyGate::getProperty(Local<String> name,
|
111
132
|
const PropertyCallbackInfo<Value> &info) {
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
133
|
+
with_gvl(this, [&] {
|
134
|
+
VALUE rb_args = rb_ary_new2(2);
|
135
|
+
rb_ary_push(rb_args, ruby_object);
|
136
|
+
rb_ary_push(rb_args, context->to_ruby(name));
|
137
|
+
rescued_call(rb_args, secure_call, [&] (VALUE res) {
|
138
|
+
info.GetReturnValue().Set(context->to_js(res));
|
139
|
+
});
|
140
|
+
});
|
118
141
|
}
|
119
142
|
|
120
|
-
void h8::RubyGate::setProperty(Local<String> name,
|
121
|
-
Local<Value> value,
|
143
|
+
void h8::RubyGate::setProperty(Local<String> name, Local<Value> value,
|
122
144
|
const PropertyCallbackInfo<Value> &info) {
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
145
|
+
with_gvl(this, [&] {
|
146
|
+
VALUE rb_args = rb_ary_new2(3);
|
147
|
+
rb_ary_push(rb_args, context->to_ruby(value));
|
148
|
+
rb_ary_push(rb_args, ruby_object);
|
149
|
+
VALUE method = context->to_ruby(name);
|
150
|
+
method = rb_str_cat2(method, "=");
|
151
|
+
rb_ary_push(rb_args, method);
|
152
|
+
rescued_call(rb_args, secure_call, [&] (VALUE res) {
|
153
|
+
info.GetReturnValue().Set(context->to_js(res));
|
154
|
+
});
|
155
|
+
});
|
132
156
|
}
|
133
157
|
|
134
158
|
void h8::RubyGate::getIndex(uint32_t index,
|
135
159
|
const PropertyCallbackInfo<Value> &info) {
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
160
|
+
with_gvl(this, [&] {
|
161
|
+
VALUE rb_args = rb_ary_new2(3);
|
162
|
+
rb_ary_push(rb_args, INT2FIX(index));
|
163
|
+
rb_ary_push(rb_args, ruby_object);
|
164
|
+
rb_ary_push(rb_args, rb_str_new2("[]"));
|
165
|
+
rescued_call(rb_args, secure_call, [&] (VALUE res) {
|
166
|
+
info.GetReturnValue().Set(context->to_js(res));
|
167
|
+
});
|
168
|
+
});
|
143
169
|
}
|
144
170
|
|
145
|
-
void h8::RubyGate::setIndex(uint32_t index,
|
146
|
-
Local<Value> value,
|
171
|
+
void h8::RubyGate::setIndex(uint32_t index, Local<Value> value,
|
147
172
|
const PropertyCallbackInfo<Value> &info) {
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
173
|
+
with_gvl(this, [&] {
|
174
|
+
VALUE rb_args = rb_ary_new2(4);
|
175
|
+
rb_ary_push(rb_args, INT2FIX(index));
|
176
|
+
rb_ary_push(rb_args, context->to_ruby(value));
|
177
|
+
rb_ary_push(rb_args, ruby_object);
|
178
|
+
rb_ary_push(rb_args, rb_str_new2("[]="));
|
179
|
+
rescued_call(rb_args, secure_call, [&] (VALUE res) {
|
180
|
+
info.GetReturnValue().Set(context->to_js(res));
|
181
|
+
});
|
182
|
+
});
|
156
183
|
}
|
157
184
|
|
data/ext/h8/ruby_gate.h
CHANGED
@@ -54,27 +54,35 @@ public:
|
|
54
54
|
virtual ~RubyGate() {
|
55
55
|
}
|
56
56
|
|
57
|
+
Isolate* isolate() const noexcept {
|
58
|
+
return context->getIsolate();
|
59
|
+
}
|
60
|
+
|
61
|
+
H8* getH8() {
|
62
|
+
return context;
|
63
|
+
}
|
64
|
+
|
57
65
|
protected:
|
58
66
|
/**
|
59
67
|
* Perform rb_rescue call to 'call' callback, and invoke block with value returned by callback
|
60
68
|
* unless a ruby exception is caught, in which a correct JsError is thrown.
|
61
69
|
*/
|
62
|
-
void rescued_call(VALUE rb_args, VALUE (*call)(VALUE),
|
70
|
+
void rescued_call(VALUE rb_args, VALUE (*call)(VALUE),
|
71
|
+
const std::function<void(VALUE)> &block);
|
63
72
|
|
64
73
|
/**
|
65
74
|
* Convert some v8 parameters-like object to ruby arguments array, allocating
|
66
75
|
* extra slots in array if need
|
67
76
|
*/
|
68
|
-
template
|
77
|
+
template<class T>
|
69
78
|
VALUE ruby_args(const T& args, unsigned extras = 0) {
|
70
79
|
unsigned n = args.Length();
|
71
|
-
VALUE rb_args = rb_ary_new2(n+extras);
|
80
|
+
VALUE rb_args = rb_ary_new2(n + extras);
|
72
81
|
for (unsigned i = 0; i < n; i++)
|
73
82
|
rb_ary_push(rb_args, context->to_ruby(args[i]));
|
74
83
|
return rb_args;
|
75
84
|
}
|
76
85
|
|
77
|
-
|
78
86
|
/**
|
79
87
|
* Ruby callable callback for rb_rescue and like. Args [0..-2] are call arguments,
|
80
88
|
* last arg[-1] should be a callable to perform call with (for performance
|
@@ -90,28 +98,34 @@ protected:
|
|
90
98
|
/**
|
91
99
|
* callback for rb_rescue. Sets last_ruby_error.
|
92
100
|
*/
|
93
|
-
static VALUE rescue_callback(VALUE me,VALUE exception_object);
|
101
|
+
static VALUE rescue_callback(VALUE me, VALUE exception_object);
|
94
102
|
|
95
|
-
void getProperty(Local<String> name,
|
96
|
-
|
103
|
+
void getProperty(Local<String> name,
|
104
|
+
const PropertyCallbackInfo<Value> &info);
|
105
|
+
void setProperty(Local<String> name, Local<Value> value,
|
106
|
+
const PropertyCallbackInfo<Value> &info);
|
97
107
|
|
98
108
|
void getIndex(uint32_t index, const PropertyCallbackInfo<Value> &info);
|
99
|
-
void setIndex(uint32_t index, Local<Value> value,
|
109
|
+
void setIndex(uint32_t index, Local<Value> value,
|
110
|
+
const PropertyCallbackInfo<Value> &info);
|
100
111
|
private:
|
101
112
|
|
102
113
|
void doObjectCallback(const v8::FunctionCallbackInfo<v8::Value>& args);
|
103
114
|
|
104
115
|
static void ObjectCallback(const v8::FunctionCallbackInfo<v8::Value>& args);
|
105
116
|
|
106
|
-
static void mapGet(Local<String> name,
|
107
|
-
|
117
|
+
static void mapGet(Local<String> name,
|
118
|
+
const PropertyCallbackInfo<Value> &info);
|
119
|
+
static void mapSet(Local<String> name, Local<Value> value,
|
120
|
+
const PropertyCallbackInfo<Value> &info);
|
108
121
|
|
109
|
-
static void indexGet(uint32_t index,
|
110
|
-
|
122
|
+
static void indexGet(uint32_t index,
|
123
|
+
const PropertyCallbackInfo<Value> &info);
|
124
|
+
static void indexSet(uint32_t index, Local<Value> value,
|
125
|
+
const PropertyCallbackInfo<Value> &info);
|
111
126
|
|
112
127
|
void throw_js();
|
113
128
|
|
114
|
-
|
115
129
|
friend class H8;
|
116
130
|
|
117
131
|
H8 *context;
|
data/lib/h8/coffee.rb
CHANGED
@@ -25,7 +25,7 @@ module H8
|
|
25
25
|
# Compile coffeescript and return javascript. Keyword parameters are
|
26
26
|
# passed to H8::Context#eval - like time limits and so on.
|
27
27
|
#
|
28
|
-
# This method IS THREAD SAFE, it shares single
|
28
|
+
# This method IS THREAD SAFE, though it shares single
|
29
29
|
# compiler instance across all threads with a mutex.
|
30
30
|
def self.compile src, ** kwargs
|
31
31
|
@@mutex.synchronize {
|
data/lib/h8/version.rb
CHANGED
data/spec/context_spec.rb
CHANGED
@@ -4,16 +4,16 @@ require 'h8'
|
|
4
4
|
describe 'context' do
|
5
5
|
|
6
6
|
it 'should create' do
|
7
|
-
cxt
|
7
|
+
cxt = H8::Context.new
|
8
8
|
cxt[:one] = 1
|
9
9
|
cxt.eval("");
|
10
10
|
# cxt.eval("'Res: ' + (2+5);")
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'should gate simple values to JS context' do
|
14
|
-
cxt
|
14
|
+
cxt = H8::Context.new foo: 'hello', bar: 'world'
|
15
15
|
cxt[:sign] = '!'
|
16
|
-
res
|
16
|
+
res = cxt.eval "foo+' '+bar+sign;"
|
17
17
|
res.should == 'hello world!'
|
18
18
|
cxt.set_all one: 101, real: 1.21
|
19
19
|
cxt.eval("one + one;").should == 202
|
@@ -21,25 +21,25 @@ describe 'context' do
|
|
21
21
|
end
|
22
22
|
|
23
23
|
it 'should gate H8::Values back to JS context' do
|
24
|
-
cxt
|
25
|
-
obj
|
24
|
+
cxt = H8::Context.new
|
25
|
+
obj = cxt.eval "('che bel');"
|
26
26
|
cxt[:first] = obj
|
27
|
-
res
|
27
|
+
res = cxt.eval "first + ' giorno';"
|
28
28
|
res.should == 'che bel giorno'
|
29
29
|
end
|
30
30
|
|
31
31
|
it 'should not gate H8::Values between contexts' do
|
32
|
-
cxt
|
33
|
-
obj
|
32
|
+
cxt = H8::Context.new
|
33
|
+
obj = cxt.eval "({res: 'che bel'});"
|
34
34
|
# This should be ok
|
35
35
|
cxt[:first] = obj
|
36
|
-
res
|
36
|
+
res = cxt.eval "first.res + ' giorno';"
|
37
37
|
res.should == 'che bel giorno'
|
38
38
|
# And that should fail
|
39
39
|
cxt1 = H8::Context.new
|
40
|
-
expect(
|
40
|
+
expect(-> {
|
41
41
|
cxt1[:first] = obj
|
42
|
-
res
|
42
|
+
res = cxt1.eval "first.res + ' giorno';"
|
43
43
|
}).to raise_error(H8::Error)
|
44
44
|
end
|
45
45
|
|
@@ -54,7 +54,7 @@ describe 'context' do
|
|
54
54
|
end
|
55
55
|
|
56
56
|
it 'should limit script execution time' do
|
57
|
-
cxt
|
57
|
+
cxt = H8::Context.new
|
58
58
|
# cxt[:print] = -> (*args) { puts args.join(' ')}
|
59
59
|
script = <<-End
|
60
60
|
var start = new Date();
|
@@ -67,34 +67,61 @@ describe 'context' do
|
|
67
67
|
End
|
68
68
|
# end
|
69
69
|
t = Time.now
|
70
|
-
expect(
|
70
|
+
expect(-> {
|
71
71
|
c2 = cxt.eval script, max_time: 0.2
|
72
72
|
}).to raise_error(H8::TimeoutError)
|
73
73
|
(Time.now - t).should < 0.25
|
74
74
|
cxt.eval('(last-start)/1000').should < 250
|
75
75
|
end
|
76
76
|
|
77
|
+
it 'should have generators' do
|
78
|
+
script = <<-End
|
79
|
+
function* f(base) {
|
80
|
+
var count = base;
|
81
|
+
var end = base +3;
|
82
|
+
print("F is calleed", count);
|
83
|
+
while(count < end) {
|
84
|
+
var r = yield count++;
|
85
|
+
print("yield returned", r);
|
86
|
+
}
|
87
|
+
}
|
88
|
+
var g = f(100);
|
89
|
+
print(g.next().value);
|
90
|
+
print(g.next(1).value);
|
91
|
+
End
|
92
|
+
c = H8::Context.new
|
93
|
+
c[:print] = -> (*args) { args.join(' ') }
|
94
|
+
c.eval script
|
95
|
+
end
|
96
|
+
|
77
97
|
it 'should work in many threads' do
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
98
|
+
# pending
|
99
|
+
sum = 0
|
100
|
+
mutex = Mutex.new
|
101
|
+
valid = 0
|
102
|
+
n = 10
|
103
|
+
tt = []
|
104
|
+
n.times { |n|
|
105
|
+
cxt = nil
|
106
|
+
t = Thread.start {
|
107
|
+
cxt = H8::Context.new
|
87
108
|
cxt[:array] = 100024.times.map { |x| x*(n+1) }
|
88
|
-
cxt[:n]
|
89
|
-
|
109
|
+
cxt[:n] = n+1
|
110
|
+
mutex.synchronize {
|
111
|
+
valid += (n+1)*100 + 10
|
112
|
+
sum += cxt.eval('result = array[100] + 10')
|
113
|
+
}
|
114
|
+
tt << OpenStruct.new({ thread: t, number: n, context: cxt })
|
90
115
|
}
|
91
116
|
}
|
92
|
-
tt.each
|
93
|
-
|
117
|
+
tt.each{ |x| x.thread.join }
|
118
|
+
mutex.synchronize {
|
119
|
+
sum.should == valid
|
120
|
+
}
|
94
121
|
GC.start
|
95
122
|
# Cross-thread access
|
96
|
-
|
97
|
-
s, n =
|
123
|
+
tt.each { |x|
|
124
|
+
s, n = x.context.eval('data = [result,n]')
|
98
125
|
s.should == 100*n + 10
|
99
126
|
}
|
100
127
|
end
|
data/spec/ruby_gate_spec.rb
CHANGED
@@ -5,7 +5,9 @@ describe 'ruby gate' do
|
|
5
5
|
|
6
6
|
it 'should gate callables' do
|
7
7
|
cxt = H8::Context.new
|
8
|
+
count = 0
|
8
9
|
cxt[:fn] = -> (a, b) {
|
10
|
+
count += 1
|
9
11
|
a + b
|
10
12
|
}
|
11
13
|
|
@@ -14,6 +16,7 @@ describe 'ruby gate' do
|
|
14
16
|
cxt = nil
|
15
17
|
res = nil
|
16
18
|
GC.start
|
19
|
+
count.should == 1
|
17
20
|
end
|
18
21
|
|
19
22
|
# it 'should gate callables in therubyrace mode' do
|
@@ -47,7 +50,7 @@ describe 'ruby gate' do
|
|
47
50
|
res.to_i.should == -11
|
48
51
|
end
|
49
52
|
|
50
|
-
it 'should gate callables with varargs' do
|
53
|
+
it 'va: should gate callables with varargs' do
|
51
54
|
cxt = H8::Context.new
|
52
55
|
cxt[:fn] = -> (*args) {
|
53
56
|
args.reduce(0) { |all, x| all+x }
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'h8'
|
3
|
+
|
4
|
+
describe 'threading' do
|
5
|
+
before do
|
6
|
+
@counter_script = <<-End
|
7
|
+
var count = 0;
|
8
|
+
var endTime = end*1000 + 150;
|
9
|
+
while( new Date().getTime() < endTime ) count++;
|
10
|
+
(count);
|
11
|
+
End
|
12
|
+
@context = H8::Context.new
|
13
|
+
@context[:print] = -> (*args) {
|
14
|
+
puts "D: #{args.join(',')}"
|
15
|
+
}
|
16
|
+
Thread.pass
|
17
|
+
@end_time = Time.now.to_i + 1
|
18
|
+
@context[:end] = @end_time
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'without tout: should run JS/ruby threads in parallel' do
|
22
|
+
cnt2 = 0
|
23
|
+
Thread.start {
|
24
|
+
cnt2 += 1 while Time.now.to_i < @end_time
|
25
|
+
}
|
26
|
+
res = @context.eval @counter_script, timeout: 5000
|
27
|
+
cnt = cnt2
|
28
|
+
fail "JS thread does not run in parallel" if res < 1
|
29
|
+
fail "JS thread does not run in parallel" if cnt < 1
|
30
|
+
(res / cnt).should < 16
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
it 'should run JS callables in threads in parallel' do
|
35
|
+
fn = @context.eval "res = function (end) { #{@counter_script} return count; }"
|
36
|
+
cnt2 = 0
|
37
|
+
Thread.start {
|
38
|
+
cnt2 += 1 while Time.now.to_i < @end_time
|
39
|
+
}
|
40
|
+
res = fn.call(@end_time)
|
41
|
+
cnt = cnt2
|
42
|
+
fail "JS thread does not run in parallel" if res < 1
|
43
|
+
fail "JS thread does not run in parallel" if cnt < 1
|
44
|
+
(res / cnt).should < 16
|
45
|
+
end
|
46
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: h8
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- sergeych
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -113,6 +113,7 @@ files:
|
|
113
113
|
- spec/js_gate_spec.rb
|
114
114
|
- spec/ruby_gate_spec.rb
|
115
115
|
- spec/spec_helper.rb
|
116
|
+
- spec/threading_spec.rb
|
116
117
|
homepage: https://github.com/sergeych/hybrid8
|
117
118
|
licenses:
|
118
119
|
- MIT
|
@@ -134,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
134
135
|
version: '0'
|
135
136
|
requirements: []
|
136
137
|
rubyforge_project:
|
137
|
-
rubygems_version: 2.
|
138
|
+
rubygems_version: 2.2.2
|
138
139
|
signing_key:
|
139
140
|
specification_version: 4
|
140
141
|
summary: Minimalistic and fast Ruby <--> V8 integration
|
@@ -144,3 +145,4 @@ test_files:
|
|
144
145
|
- spec/js_gate_spec.rb
|
145
146
|
- spec/ruby_gate_spec.rb
|
146
147
|
- spec/spec_helper.rb
|
148
|
+
- spec/threading_spec.rb
|