h8 0.1.4 → 0.2.1
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 +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
|