h8 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +54 -13
- data/ext/h8/h8.cpp +4 -4
- data/ext/h8/h8.h +18 -12
- data/lib/h8/context.rb +2 -2
- data/lib/h8/value.rb +12 -8
- data/lib/h8/version.rb +1 -1
- data/lib/h8.rb +15 -1
- data/spec/ruby_gate_spec.rb +61 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 157a38dc54ce46cae280e12cd780c074d1b40c4c
|
4
|
+
data.tar.gz: 769f4a9417ae920f2b293b8bff43d10b8dbe2925
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2cae98765a9949524355278bde88517b11aeaeac1ce539f912a4e71ad3515795861898e3e87acc7431cee1c947a0298dbb1117308f383cbacf226ff279447982
|
7
|
+
data.tar.gz: 013268e403ee3be27c74656aef90287c3ea04cab5199fca72480c7c7289a4f115429da0ba11f46846b79be8a9ea98410d2bab8534ce6a235e68492e7beae000d
|
data/README.md
CHANGED
@@ -1,11 +1,21 @@
|
|
1
1
|
# Hybrid8, aka H8
|
2
2
|
|
3
|
-
_Warning_ this gem
|
4
|
-
|
5
|
-
will start from 0.1.*.
|
3
|
+
_Warning_ this gem is a public beta at the moment - beta testers are welcome!It means, it is not
|
4
|
+
yet production stable - we haven't yet tried.
|
6
5
|
|
7
|
-
|
8
|
-
|
6
|
+
This gem was intended to replace therubyracer for many reasons:
|
7
|
+
|
8
|
+
* therubyracer has critical bugs that are not fixed for a long time, under load it produces
|
9
|
+
numerous frequent crashes.
|
10
|
+
|
11
|
+
* therubyracer still uses antique version of V8, H8 uses the latest 3.31 branch
|
12
|
+
|
13
|
+
* H8 is designed to provide very tight and effective integration of two allocation systems and
|
14
|
+
object models, passing the same objects between different systems wrapping and unwrapping them
|
15
|
+
rather than copying and changing
|
16
|
+
|
17
|
+
* We hope that by the cost non significant changes we will provide faster execution. And might v8
|
18
|
+
modern branch will help us with it ;)
|
9
19
|
|
10
20
|
Special features:
|
11
21
|
|
@@ -26,19 +36,50 @@ uncaught javascript exceptions raise ruby error in ruby code.
|
|
26
36
|
|
27
37
|
## Main difference from therubyracer/features not ready
|
28
38
|
|
29
|
-
|
30
|
-
H8::Context and its returned values from concurrent threads. Use Mutexes
|
39
|
+
- This version is not (yet?) thread safe*. For the sake of effectiveness, do not access same
|
40
|
+
H8::Context and its returned values from concurrent threads. Use Mutexes if need.
|
41
|
+
|
42
|
+
The pity thing is, if we will Lock() the context on each call to it, the performance degradation
|
43
|
+
will be notable and no matter whether you need threading with it. So, if you really need it, you
|
44
|
+
wrap it with Mutex or whatever you want, without slowing down all the rest of us.
|
45
|
+
|
46
|
+
- Script is executed in the calling ruby thread without unblocking it (other ruby threads can not
|
47
|
+
perform on this core while javascript code runs).
|
48
|
+
|
49
|
+
The same performance reason. If we release gvl on script start and reacquire it every time the ruby
|
50
|
+
object, callback, class, whatever is referenced from JS, the execution will considerably degrade. As
|
51
|
+
this gem is intended to let javascript/coffescript tightly integrate with ruby objects we have
|
52
|
+
decided to not to slow down everything. If it is a problem, lets discuss it.
|
31
53
|
|
32
54
|
- correct and accurate object tracking in both JS and Ruby VMs, GC aware.
|
33
55
|
|
34
|
-
- passed
|
35
|
-
|
56
|
+
- labmda/proc passed as var to the context **does not receives first (this) argument
|
57
|
+
automatically!**
|
58
|
+
|
59
|
+
E.g. rubyracer code
|
60
|
+
|
61
|
+
cxt[:fn] = -> (this, a, b) { a + b }
|
62
|
+
|
63
|
+
Becomes
|
64
|
+
|
65
|
+
cxt[:fn] = -> (a, b) { a + b }
|
66
|
+
|
67
|
+
it looks prettier, doesn't it? And, if you really need `this` in the callable, just mention it in
|
68
|
+
the call:
|
69
|
+
|
70
|
+
cxt[:fn] = -> (this, a, b) { a + b + this.offset }
|
71
|
+
cxt.eval 'fn(this, 10, 20)'
|
72
|
+
|
73
|
+
This, again, is done for execution speed. Always wrapping `this` to pass it to ruby is a costly
|
74
|
+
procedure which is always performed in rubyracer - no matter is it really needed. In H8 you can
|
75
|
+
spend your resources only when it worth extra processing. From my experience, it is a rare situation
|
76
|
+
when such a lambda needs javascript's this - but, no problem, pass it this, or whatever else
|
77
|
+
you need ;)
|
78
|
+
|
79
|
+
- there is no 'Context object initialization' - it does not work well in rubyracer so it is not
|
80
|
+
likely used widely. We can add it later, though.
|
36
81
|
|
37
|
-
- Not Yet: source information in uncaught exception in js
|
38
82
|
|
39
|
-
- Script is executed in the calling ruby thread without unblocking it (for the sake of
|
40
|
-
effectiveness). If we would release GIL and reacquire it, it would take more time. And there is no
|
41
|
-
multithreading support yet (this one might be added soon).
|
42
83
|
|
43
84
|
## Installation
|
44
85
|
|
data/ext/h8/h8.cpp
CHANGED
@@ -21,13 +21,13 @@ void h8::JsError::raise() {
|
|
21
21
|
// Passing thru the Ruby exception
|
22
22
|
ruby_exception = rg->rubyObject();
|
23
23
|
} else {
|
24
|
-
Local<
|
24
|
+
Local<Message> m = message();
|
25
|
+
Local<String> s = m->Get();
|
25
26
|
String::Utf8Value res(s->ToString());
|
26
27
|
ruby_exception = ruby_exception = rb_exc_new2(js_exception,
|
27
|
-
*res ? *res : "
|
28
|
+
*res ? *res : "unknown javascript exception");
|
28
29
|
rb_iv_set(ruby_exception, "@message", h8->to_ruby(s));
|
29
|
-
|
30
|
-
rb_iv_set(ruby_exception, "@source", h8->to_ruby(source));
|
30
|
+
rb_iv_set(ruby_exception, "@javascript_error", h8->to_ruby(jsx));
|
31
31
|
}
|
32
32
|
}
|
33
33
|
rb_exc_raise(ruby_exception);
|
data/ext/h8/h8.h
CHANGED
@@ -45,7 +45,7 @@ public:
|
|
45
45
|
reason = str_reason;
|
46
46
|
}
|
47
47
|
|
48
|
-
JsError(H8* h8,
|
48
|
+
JsError(H8* h8, Local<Message> message, Local<Value> exception);
|
49
49
|
|
50
50
|
/**
|
51
51
|
* Call it with a proper exception class and be careful - after this call no code will be executed!
|
@@ -56,6 +56,8 @@ public:
|
|
56
56
|
|
57
57
|
Local<Value> exception() const;
|
58
58
|
|
59
|
+
Local<Value> stacktrace() const;
|
60
|
+
|
59
61
|
virtual ~JsError() noexcept {
|
60
62
|
_message.Reset();
|
61
63
|
_exception.Reset();
|
@@ -68,9 +70,11 @@ protected:
|
|
68
70
|
v8::Persistent<v8::Value, v8::CopyablePersistentTraits<v8::Value>> _exception;
|
69
71
|
};
|
70
72
|
|
71
|
-
class JsTimeoutError
|
73
|
+
class JsTimeoutError: public JsError {
|
72
74
|
public:
|
73
|
-
JsTimeoutError(H8* h8) :
|
75
|
+
JsTimeoutError(H8* h8) :
|
76
|
+
JsError(h8, NULL) {
|
77
|
+
}
|
74
78
|
virtual void raise();
|
75
79
|
};
|
76
80
|
|
@@ -97,6 +101,7 @@ public:
|
|
97
101
|
isolate = Isolate::New();
|
98
102
|
Isolate::Scope isolate_scope(isolate);
|
99
103
|
HandleScope handle_scope(isolate);
|
104
|
+
isolate->SetCaptureStackTraceForUncaughtExceptions(true);
|
100
105
|
|
101
106
|
v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(
|
102
107
|
isolate);
|
@@ -113,11 +118,11 @@ public:
|
|
113
118
|
* to this value, JsTimeoutError will be thrown if exceeded
|
114
119
|
* \return the value returned by the script.
|
115
120
|
*/
|
116
|
-
Handle<Value> eval(const char* script_utf,unsigned max_ms=0);
|
121
|
+
Handle<Value> eval(const char* script_utf, unsigned max_ms = 0);
|
117
122
|
|
118
|
-
VALUE eval_to_ruby(const char* script_utf,int timeout=0) {
|
123
|
+
VALUE eval_to_ruby(const char* script_utf, int timeout = 0) {
|
119
124
|
// TODO: throw ruby exception on error
|
120
|
-
return to_ruby(eval(script_utf,timeout));
|
125
|
+
return to_ruby(eval(script_utf, timeout));
|
121
126
|
}
|
122
127
|
|
123
128
|
Handle<Context> getContext() {
|
@@ -210,10 +215,13 @@ inline VALUE h8::H8::to_ruby(Handle<Value> value) {
|
|
210
215
|
return JsGate::to_ruby(this, value);
|
211
216
|
}
|
212
217
|
|
213
|
-
inline h8::JsError::JsError(H8* h8,
|
214
|
-
|
215
|
-
h8(h8),
|
216
|
-
|
218
|
+
inline h8::JsError::JsError(H8* h8, Local<Message> message,
|
219
|
+
Local<Value> exception) :
|
220
|
+
h8(h8),
|
221
|
+
_message(h8->getIsolate(), message),
|
222
|
+
_exception(h8->getIsolate(), exception),
|
223
|
+
has_js_exception(true),
|
224
|
+
reason(NULL) {
|
217
225
|
}
|
218
226
|
|
219
227
|
inline Local<Message> h8::JsError::message() const {
|
@@ -224,6 +232,4 @@ inline Local<Value> h8::JsError::exception() const {
|
|
224
232
|
return Local<Value>::New(h8->getIsolate(), _exception);
|
225
233
|
}
|
226
234
|
|
227
|
-
|
228
|
-
|
229
235
|
#endif
|
data/lib/h8/context.rb
CHANGED
@@ -37,6 +37,7 @@ module H8
|
|
37
37
|
# @raise [H8::TimeoutError] if the timeout was set and expired
|
38
38
|
def eval script, max_time: 0, timeout: 0
|
39
39
|
timeout = max_time * 1000 if max_time > 0
|
40
|
+
yield(self) if block_given?
|
40
41
|
_eval script, timeout.to_i
|
41
42
|
end
|
42
43
|
|
@@ -83,7 +84,7 @@ module H8
|
|
83
84
|
H8::Undefined
|
84
85
|
end
|
85
86
|
|
86
|
-
|
87
|
+
protected
|
87
88
|
|
88
89
|
# Set var that could be either a callable, class instance, simple value or a Class class
|
89
90
|
# in which case constructor function will be created
|
@@ -106,6 +107,5 @@ module H8
|
|
106
107
|
def _do_cretate_ruby_class(klass, arguments)
|
107
108
|
klass.new *arguments.to_ruby.values
|
108
109
|
end
|
109
|
-
|
110
110
|
end
|
111
111
|
end
|
data/lib/h8/value.rb
CHANGED
@@ -19,7 +19,7 @@ module H8
|
|
19
19
|
include Comparable
|
20
20
|
|
21
21
|
def inspect
|
22
|
-
"<H8::Value #{to_ruby}>"
|
22
|
+
"<H8::Value #{to_ruby rescue '(too deep)'}>"
|
23
23
|
end
|
24
24
|
|
25
25
|
# Get js object attribute by either name or index (should be Fixnum instance). It always
|
@@ -105,8 +105,8 @@ module H8
|
|
105
105
|
end
|
106
106
|
|
107
107
|
# Try to convert javascript object to a ruby hash
|
108
|
-
def to_h
|
109
|
-
each.reduce({}) { |all, kv| all[kv[0]] = kv[1].to_ruby; all }
|
108
|
+
def to_h depth=0
|
109
|
+
each.reduce({}) { |all, kv| all[kv[0]] = kv[1].to_ruby depth; all }
|
110
110
|
end
|
111
111
|
|
112
112
|
# Iterate over javascript object keys
|
@@ -125,8 +125,12 @@ module H8
|
|
125
125
|
end
|
126
126
|
|
127
127
|
# Tries to convert wrapped JS object to ruby primitive (Fixed, String, Float, Array, Hash).
|
128
|
-
# Note that this conversion looses information about source javascript class (if any)
|
129
|
-
|
128
|
+
# Note that this conversion looses information about source javascript class (if any).
|
129
|
+
#
|
130
|
+
# @raise H8::Error if the data structure is too deep (e.g. cyclic)
|
131
|
+
def to_ruby depth=0
|
132
|
+
depth += 1
|
133
|
+
raise H8::Error, "object tree too deep" if depth > 100
|
130
134
|
case
|
131
135
|
when integer?
|
132
136
|
to_i
|
@@ -135,11 +139,11 @@ module H8
|
|
135
139
|
when float?
|
136
140
|
to_f
|
137
141
|
when array?
|
138
|
-
_get_attr('length').to_i.times.map { |i| _get_index(i).to_ruby }
|
142
|
+
_get_attr('length').to_i.times.map { |i| _get_index(i).to_ruby depth }
|
139
143
|
when function?
|
140
144
|
to_proc
|
141
145
|
when object?
|
142
|
-
to_h
|
146
|
+
to_h depth
|
143
147
|
else
|
144
148
|
raise Error, "Dont know how to convert #{self.class}"
|
145
149
|
end
|
@@ -179,7 +183,7 @@ end
|
|
179
183
|
class Object
|
180
184
|
# It is already a ruby object. Gate objects should override
|
181
185
|
# as need
|
182
|
-
def to_ruby
|
186
|
+
def to_ruby depth=0
|
183
187
|
self
|
184
188
|
end
|
185
189
|
end
|
data/lib/h8/version.rb
CHANGED
data/lib/h8.rb
CHANGED
@@ -12,12 +12,26 @@ module H8
|
|
12
12
|
# The general error caused by the script execution, e.g. uncaught javascript exceptinos and like.
|
13
13
|
# Check #message to see the cause.
|
14
14
|
class JsError < Error
|
15
|
+
# Error message
|
15
16
|
attr :message
|
16
|
-
|
17
|
+
|
18
|
+
# Javascript Error object. May be nil
|
19
|
+
attr :javascript_error
|
17
20
|
|
18
21
|
def to_s
|
19
22
|
message
|
20
23
|
end
|
24
|
+
|
25
|
+
# Error name
|
26
|
+
def name
|
27
|
+
@javascript_error.name ? @javascript_error.name : message
|
28
|
+
end
|
29
|
+
|
30
|
+
# String that represents stack trace if any as multiline string (\n separated)
|
31
|
+
def javascript_backtrace
|
32
|
+
@javascript_error ? @javascript_error.stack : message
|
33
|
+
end
|
34
|
+
|
21
35
|
end
|
22
36
|
|
23
37
|
# Script execution is timed out (see H8::Context#eval timeout parameter)
|
data/spec/ruby_gate_spec.rb
CHANGED
@@ -16,6 +16,36 @@ describe 'ruby gate' do
|
|
16
16
|
GC.start
|
17
17
|
end
|
18
18
|
|
19
|
+
# it 'should gate callables in therubyrace mode' do
|
20
|
+
# cxt = H8::CompatibleContext.new
|
21
|
+
# cxt[:fn] = -> (this, a, b) {
|
22
|
+
# p this.to_s
|
23
|
+
# this.offset + a + b
|
24
|
+
# }
|
25
|
+
#
|
26
|
+
# res = cxt.eval <<-End
|
27
|
+
# function Test() {
|
28
|
+
# this.offset = 110;
|
29
|
+
# this.method = function(a,b) {
|
30
|
+
# return fn(a,b);
|
31
|
+
# }
|
32
|
+
# }
|
33
|
+
# new Test().method(11, 22);
|
34
|
+
# End
|
35
|
+
# res.to_i.should == 143
|
36
|
+
# end
|
37
|
+
|
38
|
+
it 'should allow edit context on yield' do
|
39
|
+
cxt = H8::Context.new
|
40
|
+
cxt[:fn] = -> (a, b) {
|
41
|
+
a + b
|
42
|
+
}
|
43
|
+
res = cxt.eval("fn(11, 22);") { |cxt|
|
44
|
+
cxt[:fn] = -> (a,b) { a - b }
|
45
|
+
}
|
46
|
+
res.to_i.should == -11
|
47
|
+
end
|
48
|
+
|
19
49
|
it 'should gate callables with varargs' do
|
20
50
|
cxt = H8::Context.new
|
21
51
|
cxt[:fn] = -> (*args) {
|
@@ -95,7 +125,29 @@ describe 'ruby gate' do
|
|
95
125
|
}
|
96
126
|
end
|
97
127
|
|
98
|
-
it 'should have information about javascript exceptions'
|
128
|
+
it 'should have information about javascript exceptions' do
|
129
|
+
# javascript_backtrace - list of strings?
|
130
|
+
begin
|
131
|
+
H8::Context.eval <<-End
|
132
|
+
// This is ok
|
133
|
+
var ok = true;
|
134
|
+
function bad() {
|
135
|
+
throw Error("test");
|
136
|
+
}
|
137
|
+
function good() {
|
138
|
+
bad();
|
139
|
+
}
|
140
|
+
// This is also ok
|
141
|
+
good();
|
142
|
+
End
|
143
|
+
fail 'did not raise error'
|
144
|
+
rescue H8::JsError => e
|
145
|
+
x = e.javascript_error
|
146
|
+
e.name.should == 'Error'
|
147
|
+
e.message.should =~ /test/
|
148
|
+
e.javascript_backtrace.should =~ /at bad \(eval\:4\:17\)/
|
149
|
+
end
|
150
|
+
end
|
99
151
|
|
100
152
|
context 'accessing ruby code' do
|
101
153
|
class Base
|
@@ -285,4 +337,12 @@ describe 'ruby gate' do
|
|
285
337
|
cxt.eval('rc.init_args').should == ['hello', 'world']
|
286
338
|
end
|
287
339
|
end
|
340
|
+
|
341
|
+
it 'should survive recursive constructions' do
|
342
|
+
a = H8::Context.eval 'a=[1,2]; a.push(a); a'
|
343
|
+
expect(-> { a.to_ruby }).to raise_error(H8::Error)
|
344
|
+
a = H8::Context.eval "a={on:2}; a['a']=a; a"
|
345
|
+
expect(-> { a.to_ruby }).to raise_error(H8::Error)
|
346
|
+
a.inspect.should =~ /too deep/
|
347
|
+
end
|
288
348
|
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.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- sergeych
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-12-
|
11
|
+
date: 2014-12-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|