ruby-await-node 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +16 -0
- data/.gitignore +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +34 -0
- data/LICENSE +22 -0
- data/README.md +92 -0
- data/lib/ruby-await-node/context.rb +71 -0
- data/lib/ruby-await-node/exception.rb +4 -0
- data/lib/ruby-await-node/runtime.rb +105 -0
- data/lib/ruby-await-node/version.rb +3 -0
- data/lib/ruby-await-node.rb +2 -0
- data/ruby-await-node.gemspec +18 -0
- data/ruby-await-node.js +69 -0
- data/spec/fork_test_harness.rb +34 -0
- data/spec/ruby_await_node_spec.rb +146 -0
- data/spec/spec_helper.rb +2 -0
- metadata +92 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 450ba09014e24cfd92d646e03dbdaa4b3bc4f867
|
4
|
+
data.tar.gz: f9b9a15aa7d56aedd26af82650f574fc53b9e947
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0cb5c3440f8bfeca49eeab6021b8aeb1efc82450cf2039c4c700921ee6fc09624f99742372b0cc8c3f4d2d25f845d9831838655026d87e9d31d6928bdeb0fdc8
|
7
|
+
data.tar.gz: c2fd46e29a35ec2bcad25b6bb3a5e9ac0e4611266bea5a51837477c9038312f2207910562fed723260826a32f741725739257efd783270e75f4fe179840211f8
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
ruby-await-node (1.2.2)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
byebug (10.0.2)
|
10
|
+
diff-lcs (1.3)
|
11
|
+
rspec (3.8.0)
|
12
|
+
rspec-core (~> 3.8.0)
|
13
|
+
rspec-expectations (~> 3.8.0)
|
14
|
+
rspec-mocks (~> 3.8.0)
|
15
|
+
rspec-core (3.8.2)
|
16
|
+
rspec-support (~> 3.8.0)
|
17
|
+
rspec-expectations (3.8.4)
|
18
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
19
|
+
rspec-support (~> 3.8.0)
|
20
|
+
rspec-mocks (3.8.1)
|
21
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
22
|
+
rspec-support (~> 3.8.0)
|
23
|
+
rspec-support (3.8.2)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
ruby
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
byebug
|
30
|
+
rspec
|
31
|
+
ruby-await-node!
|
32
|
+
|
33
|
+
BUNDLED WITH
|
34
|
+
1.16.1
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Mavenlink, Inc.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# Introduction
|
2
|
+
|
3
|
+
This was inspired by https://github.com/mavenlink/alaska
|
4
|
+
Unfortunately, alaska was designed to run every new script in a new
|
5
|
+
sandbox, thus things like require won't work. So we don't have access to
|
6
|
+
all the power offered by nodejs
|
7
|
+
|
8
|
+
Out of our need to call javascript in a rapid fashion from javascript
|
9
|
+
with minimal setup, we decided to rewrite alaska into ruby-await-node
|
10
|
+
with the goal of efficiently execute sophisticated javascript code from
|
11
|
+
ruby.
|
12
|
+
|
13
|
+
The key to the performance is to launch a nodejs webserver, then use
|
14
|
+
such webserver to dangerously eval any given javascript part.
|
15
|
+
|
16
|
+
It's risky, we know. But with great power come great responsibility. You
|
17
|
+
should be using this if you want to leverage the expertise of javascript
|
18
|
+
where ruby fall short
|
19
|
+
|
20
|
+
# TODO For ruby-await-node
|
21
|
+
|
22
|
+
- [x] Execute any javascript from ruby
|
23
|
+
- [x] Pass all test
|
24
|
+
- [ ] Execute await/async method
|
25
|
+
- [ ] Use zeromq instead of http webserver to design away port conflicting
|
26
|
+
|
27
|
+
# Getting Started
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
gem 'ruby-await-node', '~> 1.0'
|
31
|
+
```
|
32
|
+
|
33
|
+
Or if you want to run the latest version
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
gem 'ruby-await-node', :git => 'git@github.com:remitano/ruby-await-node.git'
|
37
|
+
```
|
38
|
+
|
39
|
+
Then to invoke some javascript
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
runtime = RubyAwaitNode::Runtime.new(debug: true)
|
43
|
+
context = RubyAwaitNode::Context.new(runtime)
|
44
|
+
entry_path = File.expand_path("entry.js", __dir__)
|
45
|
+
context.eval("global.actor = require(#{entry_path.to_json})")
|
46
|
+
result = context.eval("actor.method()")
|
47
|
+
```
|
48
|
+
|
49
|
+
Your entry.js may look like this:
|
50
|
+
```javascript
|
51
|
+
const moment = require("moment")
|
52
|
+
module.exports = {
|
53
|
+
method: function() {
|
54
|
+
return something;
|
55
|
+
},
|
56
|
+
|
57
|
+
asyncMethod: async function() {
|
58
|
+
await operation;
|
59
|
+
return something;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
```
|
63
|
+
|
64
|
+
Under the hood ruby-await-node will automatically wait for async method
|
65
|
+
to complete
|
66
|
+
|
67
|
+
## Examples
|
68
|
+
|
69
|
+
### Load a javascript file
|
70
|
+
```
|
71
|
+
context.load(File.expand_path("entry.js", __dir__))
|
72
|
+
```
|
73
|
+
|
74
|
+
*Warning*: always pass the absolute path
|
75
|
+
### Simple execution
|
76
|
+
```
|
77
|
+
context.eval("1.0 + 2.0")
|
78
|
+
context.eval("global.a = 1.0")
|
79
|
+
```
|
80
|
+
|
81
|
+
### Calling function with arguments
|
82
|
+
```
|
83
|
+
context.call("(function(b) { return global.a * b })", 5)
|
84
|
+
```
|
85
|
+
|
86
|
+
This will be equivalent to execute this in nodejs
|
87
|
+
```
|
88
|
+
(function(b) { return a * b }).apply(this, [5])
|
89
|
+
```
|
90
|
+
|
91
|
+
### Other examples
|
92
|
+
Look into spec/ for other potential examples
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'socket'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'json'
|
5
|
+
require_relative "exception"
|
6
|
+
|
7
|
+
module RubyAwaitNode
|
8
|
+
class Context
|
9
|
+
# runtime is an instance of RubyAwaitNode
|
10
|
+
# src is the js code to be eval()'d in the nodejs context
|
11
|
+
def initialize(runtime, src = "", options = {})
|
12
|
+
@runtime = runtime
|
13
|
+
|
14
|
+
# compile context source, in most cases
|
15
|
+
# this is something like the CoffeeScript compiler
|
16
|
+
# or the SASS compiler
|
17
|
+
eval(src)
|
18
|
+
end
|
19
|
+
|
20
|
+
def eval(src)
|
21
|
+
if /\S/ =~ src #IMPORTANT! /\S/ =~ "()" => 0
|
22
|
+
exec(src)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def exec(src)
|
27
|
+
return "" unless src.length > 0
|
28
|
+
|
29
|
+
src = src.encode('UTF-8', :undef => :replace, :replace => '')
|
30
|
+
src = compile_source(src)
|
31
|
+
|
32
|
+
# src is either an empty object
|
33
|
+
# OR a valid JSON string in the form
|
34
|
+
# ['ok', 'result-of-coffeescript-or-sass-compiler']
|
35
|
+
# OR if an error occured
|
36
|
+
# ['err', 'some sort of error to be presented to the developer as a sprockets error']
|
37
|
+
#
|
38
|
+
status, value = src.empty? ? [] : ::JSON.parse(src, create_additions: false)
|
39
|
+
if status == "ok"
|
40
|
+
value
|
41
|
+
elsif value =~ /SyntaxError:/
|
42
|
+
raise RuntimeError, value
|
43
|
+
else
|
44
|
+
raise ProgramError, value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def call(identifier, *args)
|
49
|
+
eval "#{identifier}.apply(this, #{::JSON.generate(args)})"
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def compile_source(contents)
|
55
|
+
sock = Net::BufferedIO.new(@runtime.provision_socket)
|
56
|
+
request = Net::HTTP::Post.new("/")
|
57
|
+
request['Connection'] = 'close'
|
58
|
+
request['Content-Type'] = 'application/x-www-form-urlencoded'
|
59
|
+
request.body = contents
|
60
|
+
request.exec(sock, "1.1", "/")
|
61
|
+
|
62
|
+
begin
|
63
|
+
response = Net::HTTPResponse.read_new(sock)
|
64
|
+
end while response.kind_of?(Net::HTTPContinue)
|
65
|
+
|
66
|
+
response.reading_body(sock, request.response_body_permitted?) { }
|
67
|
+
sock.close
|
68
|
+
response.body
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
require_relative 'context'
|
4
|
+
|
5
|
+
module RubyAwaitNode
|
6
|
+
class Runtime
|
7
|
+
attr_accessor :debug, :nodejs_cmd, :port, :pid, :semaphore
|
8
|
+
|
9
|
+
def initialize(opts = {})
|
10
|
+
srand
|
11
|
+
|
12
|
+
@debug = opts[:debug]
|
13
|
+
@nodejs_cmd = "node"
|
14
|
+
@pid = nil
|
15
|
+
@semaphore = Mutex.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def name
|
19
|
+
"RubyAwaitNode"
|
20
|
+
end
|
21
|
+
|
22
|
+
def available?
|
23
|
+
ENV["PATH"].split(":").detect do |path|
|
24
|
+
%w{
|
25
|
+
nodejs
|
26
|
+
node
|
27
|
+
}.detect do |node|
|
28
|
+
File.exist?(File.join(path, node)) || File.symlink?(File.join(path, node))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def deprecated?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
def context_class
|
38
|
+
RubyAwaitNode::Context
|
39
|
+
end
|
40
|
+
|
41
|
+
#NOTE: this should be thread-safe
|
42
|
+
def provision_socket
|
43
|
+
ensure_startup unless @pid
|
44
|
+
|
45
|
+
wait_socket = nil
|
46
|
+
checks = 0
|
47
|
+
max_retries = 12
|
48
|
+
|
49
|
+
while checks < max_retries
|
50
|
+
begin
|
51
|
+
checks += 1
|
52
|
+
wait_socket = UNIXSocket.new(@port)
|
53
|
+
break
|
54
|
+
rescue Errno::ENOENT, Errno::ECONNREFUSED, Errno::ENOTDIR
|
55
|
+
wait_socket = nil
|
56
|
+
sleep 0.5
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if checks >= max_retries
|
61
|
+
ensure_shutdown
|
62
|
+
raise RuntimeError, "unable to connect to ruby-await-node.js server"
|
63
|
+
end
|
64
|
+
|
65
|
+
wait_socket
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def ensure_startup
|
71
|
+
@semaphore.synchronize {
|
72
|
+
return if @pid
|
73
|
+
|
74
|
+
@port = begin
|
75
|
+
tmpfile = Tempfile.new("ruby-await-node")
|
76
|
+
path = tmpfile.path
|
77
|
+
tmpfile.close
|
78
|
+
tmpfile.unlink
|
79
|
+
path
|
80
|
+
end
|
81
|
+
|
82
|
+
ruby_await_node_js_path = File.join(File.dirname(File.expand_path(__FILE__)), '../../ruby-await-node.js')
|
83
|
+
command_options = [ruby_await_node_js_path, "--debug #{!!@debug}"] # --other --command-line --options --go --here
|
84
|
+
|
85
|
+
@initialize_pid = Process.pid
|
86
|
+
@pid = Process.spawn({"PORT" => @port.to_s}, @nodejs_cmd, *command_options, {:err => :out})
|
87
|
+
|
88
|
+
at_exit do
|
89
|
+
ensure_shutdown
|
90
|
+
end
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def ensure_shutdown
|
95
|
+
return unless @pid
|
96
|
+
return unless Process.pid == @initialize_pid
|
97
|
+
|
98
|
+
Process.kill("TERM", @pid) rescue Errno::ECHILD
|
99
|
+
Process.wait(@pid) rescue Errno::ECHILD
|
100
|
+
|
101
|
+
@port = nil
|
102
|
+
@pid = nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'ruby-await-node/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'ruby-await-node'
|
7
|
+
s.version = RubyAwaitNode::VERSION
|
8
|
+
s.summary = "Efficiently execute js from ruby by keeping a long run nodejs process"
|
9
|
+
s.description = "Efficiently execute js from ruby by keeping a long run nodejs process"
|
10
|
+
s.authors = ["Phuong Nguyen", "Jon Bardin", "Stephen Grider", "Ville Lautanala", "Giovanni Bonetti"]
|
11
|
+
s.email = 'phuongnd08@gmail.com'
|
12
|
+
s.files = `git ls-files -z`.split("\x0")
|
13
|
+
s.homepage = "https://github.com/remitano/ruby-await-node"
|
14
|
+
s.license = 'MIT'
|
15
|
+
|
16
|
+
s.add_development_dependency 'rspec'
|
17
|
+
s.add_development_dependency 'byebug'
|
18
|
+
end
|
data/ruby-await-node.js
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
const http = require("http");
|
2
|
+
|
3
|
+
const debugOpt = "--debug true";
|
4
|
+
let debug = false;
|
5
|
+
|
6
|
+
process.argv.forEach((val, index, array) => {
|
7
|
+
if (debugOpt === val) {
|
8
|
+
debug = true;
|
9
|
+
}
|
10
|
+
});
|
11
|
+
|
12
|
+
const webPrint = (res, respBody) => {
|
13
|
+
res.writeHead(200, { Connection: "keep-alive" });
|
14
|
+
res.write(respBody);
|
15
|
+
res.end();
|
16
|
+
};
|
17
|
+
|
18
|
+
const webPrintResult = (res, result) => {
|
19
|
+
webPrint(res, JSON.stringify(["ok", result]));
|
20
|
+
};
|
21
|
+
|
22
|
+
const webPrintError = (res, error) => {
|
23
|
+
webPrint(res, JSON.stringify(["err", error.toString()]));
|
24
|
+
};
|
25
|
+
|
26
|
+
function isPromise(value) {
|
27
|
+
return Boolean(value && typeof value.then === "function");
|
28
|
+
}
|
29
|
+
|
30
|
+
const server = http.createServer((req, res) => {
|
31
|
+
let contents = "";
|
32
|
+
|
33
|
+
req.on("data", (dataIn) => {
|
34
|
+
contents += dataIn;
|
35
|
+
});
|
36
|
+
|
37
|
+
req.on("end", () => {
|
38
|
+
try {
|
39
|
+
const result = eval(contents);
|
40
|
+
if (typeof result == "undefined" && result !== null) {
|
41
|
+
webPrintResult(res, null);
|
42
|
+
} else if (isPromise(result)) {
|
43
|
+
result
|
44
|
+
.then((value) => {
|
45
|
+
webPrintResult(res, value);
|
46
|
+
})
|
47
|
+
.catch((err) => {
|
48
|
+
webPrintError(res, err);
|
49
|
+
});
|
50
|
+
} else {
|
51
|
+
try {
|
52
|
+
webPrintResult(res, result);
|
53
|
+
} catch (err) {
|
54
|
+
webPrintError(res, err);
|
55
|
+
}
|
56
|
+
}
|
57
|
+
} catch (err) {
|
58
|
+
webPrintError(res, err);
|
59
|
+
}
|
60
|
+
});
|
61
|
+
});
|
62
|
+
|
63
|
+
const port = process.env.PORT || 3001;
|
64
|
+
server.listen(port);
|
65
|
+
|
66
|
+
if (debug) {
|
67
|
+
console.log("Listening on port:", port);
|
68
|
+
console.error("RubyAwaitNode.js started. Piping coffee to warmer climates.");
|
69
|
+
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ruby-await-node'
|
4
|
+
|
5
|
+
ruby_await_node_runtime = RubyAwaitNode::Runtime.new(:debug => false)
|
6
|
+
context = RubyAwaitNode::Context.new(ruby_await_node_runtime)
|
7
|
+
context.eval("global.a = 2.0")
|
8
|
+
|
9
|
+
fork_count = 4
|
10
|
+
|
11
|
+
pids = (0...fork_count).map do |i|
|
12
|
+
fork do
|
13
|
+
sleep i * 0.1 # simulate different process exit at different time to expose premature shutdown of shared nodejs process
|
14
|
+
result = context.call("(function(b) { return (global.a * b); })", i)
|
15
|
+
exit result #use the exit code to communicate the calculation result
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
exit_statuses = pids.map { |pid| Process.wait2(pid) }
|
20
|
+
|
21
|
+
results = []
|
22
|
+
|
23
|
+
exit_statuses.each do |pid, status|
|
24
|
+
results << status.exitstatus
|
25
|
+
end
|
26
|
+
|
27
|
+
# http://www.wolframalpha.com/input/?i=3rd+trianglur+number
|
28
|
+
sum = results.compact.inject(0) { |result, element| result + element }
|
29
|
+
|
30
|
+
if (sum) == 12
|
31
|
+
exit 0
|
32
|
+
else
|
33
|
+
exit 1
|
34
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RubyAwaitNode do
|
4
|
+
def return_42
|
5
|
+
"(function() { var f = 42; return 42;})()"
|
6
|
+
end
|
7
|
+
|
8
|
+
def await_return_42
|
9
|
+
"(async function() { var f = 42; await new Promise(resolve => setTimeout(resolve, 1000)); return 42;})()"
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_ruby_await_node_runtime
|
13
|
+
RubyAwaitNode::Runtime.new(:debug => false)
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_ruby_await_node_context
|
17
|
+
RubyAwaitNode::Context.new(create_ruby_await_node_runtime)
|
18
|
+
end
|
19
|
+
|
20
|
+
context "simple javascript" do
|
21
|
+
it "returns desired value" do
|
22
|
+
js_result = create_ruby_await_node_context.eval(return_42)
|
23
|
+
expect(js_result).to eq 42
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "await javascript" do
|
28
|
+
it "return desired value" do
|
29
|
+
js_result = create_ruby_await_node_context.eval(await_return_42)
|
30
|
+
expect(js_result).to eq 42
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "raises an RubyAwaitNode::ProgramError on error" do
|
35
|
+
expect {
|
36
|
+
create_ruby_await_node_context.eval("(function() { throw new Error('foo\\nbar', 0, 'test.js'); })()")
|
37
|
+
}.to raise_error(RubyAwaitNode::ProgramError)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "requires js to be in a self-executing function" do
|
41
|
+
js_result = -1
|
42
|
+
|
43
|
+
expect {
|
44
|
+
js_result = create_ruby_await_node_context.eval("return true;")
|
45
|
+
}.to raise_error(RubyAwaitNode::RuntimeError)
|
46
|
+
|
47
|
+
expect(js_result).to eq(-1)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "safely allows subsequent #eval calls after runtime error" do
|
51
|
+
js_result = create_ruby_await_node_context.eval(return_42)
|
52
|
+
expect(js_result).to eq 42
|
53
|
+
|
54
|
+
expect {
|
55
|
+
js_result = create_ruby_await_node_context.eval("return true;")
|
56
|
+
}.to raise_error(RubyAwaitNode::RuntimeError)
|
57
|
+
|
58
|
+
js_result = create_ruby_await_node_context.eval(return_42)
|
59
|
+
expect(js_result).to eq 42
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should be thread safe when sharing context between threads" do
|
63
|
+
thread_count = 128
|
64
|
+
threads = []
|
65
|
+
semaphore = Mutex.new
|
66
|
+
results = []
|
67
|
+
|
68
|
+
context = create_ruby_await_node_context
|
69
|
+
context.eval("global.a = 1.0;")
|
70
|
+
|
71
|
+
thread_count.times { |index|
|
72
|
+
threads << Thread.new {
|
73
|
+
result = context.call("(function(b) { return (global.a * b); })", index)
|
74
|
+
semaphore.synchronize {
|
75
|
+
results << result
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
threads.each { |t| t.join }
|
81
|
+
|
82
|
+
sum = results.inject(0) { |result, element| result + element }
|
83
|
+
|
84
|
+
expect(sum).to eq(8128) # http://www.wolframalpha.com/input/?i=127th+trianglur+number
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should be thread safe when creating new contexts in threads" do
|
88
|
+
thread_count = 128
|
89
|
+
threads = []
|
90
|
+
semaphore = Mutex.new
|
91
|
+
results = []
|
92
|
+
|
93
|
+
thread_count.times { |index|
|
94
|
+
threads << Thread.new {
|
95
|
+
context = create_ruby_await_node_context
|
96
|
+
context.eval("global.a = 2.0;") # this asserts that the context is shared
|
97
|
+
result = context.call("(function(b) { return (a * b); })", index)
|
98
|
+
semaphore.synchronize {
|
99
|
+
results << result
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
threads.each { |t| t.join }
|
105
|
+
|
106
|
+
sum = results.inject(0) { |result, element| result + element }
|
107
|
+
|
108
|
+
expect(sum).to eq(8128 * 2) # http://www.wolframalpha.com/input/?i=127th+trianglur+number
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should not break if exception occurs in thread" do
|
112
|
+
context_a = create_ruby_await_node_context
|
113
|
+
|
114
|
+
g_err_a = nil
|
115
|
+
g_err_b = nil
|
116
|
+
|
117
|
+
make_program_error_thread = Thread.new {
|
118
|
+
begin
|
119
|
+
_b = context_a.call("(function() { asd() })")
|
120
|
+
rescue => err_a
|
121
|
+
g_err_a = err_a
|
122
|
+
end
|
123
|
+
}
|
124
|
+
|
125
|
+
context_b = create_ruby_await_node_context
|
126
|
+
|
127
|
+
begin
|
128
|
+
_c = context_b.call("(function() { asd) })")
|
129
|
+
rescue => err_b
|
130
|
+
g_err_b = err_b
|
131
|
+
end
|
132
|
+
|
133
|
+
make_program_error_thread.join
|
134
|
+
|
135
|
+
expect(g_err_a).to be_kind_of(RubyAwaitNode::ProgramError)
|
136
|
+
expect(g_err_b).to be_kind_of(RubyAwaitNode::RuntimeError)
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should work when the context is shared between forked processes" do
|
140
|
+
fork_test_harness_pid = Process.spawn("bundle exec ruby spec/fork_test_harness.rb")
|
141
|
+
exit_pid, exit_status = Process.wait2(fork_test_harness_pid)
|
142
|
+
|
143
|
+
expect(exit_pid).to eq(fork_test_harness_pid)
|
144
|
+
expect(exit_status).to eq(0)
|
145
|
+
end
|
146
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-await-node
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Phuong Nguyen
|
8
|
+
- Jon Bardin
|
9
|
+
- Stephen Grider
|
10
|
+
- Ville Lautanala
|
11
|
+
- Giovanni Bonetti
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
date: 2019-08-11 00:00:00.000000000 Z
|
16
|
+
dependencies:
|
17
|
+
- !ruby/object:Gem::Dependency
|
18
|
+
name: rspec
|
19
|
+
requirement: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '0'
|
24
|
+
type: :development
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: byebug
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
description: Efficiently execute js from ruby by keeping a long run nodejs process
|
46
|
+
email: phuongnd08@gmail.com
|
47
|
+
executables: []
|
48
|
+
extensions: []
|
49
|
+
extra_rdoc_files: []
|
50
|
+
files:
|
51
|
+
- ".circleci/config.yml"
|
52
|
+
- ".gitignore"
|
53
|
+
- ".travis.yml"
|
54
|
+
- Gemfile
|
55
|
+
- Gemfile.lock
|
56
|
+
- LICENSE
|
57
|
+
- README.md
|
58
|
+
- lib/ruby-await-node.rb
|
59
|
+
- lib/ruby-await-node/context.rb
|
60
|
+
- lib/ruby-await-node/exception.rb
|
61
|
+
- lib/ruby-await-node/runtime.rb
|
62
|
+
- lib/ruby-await-node/version.rb
|
63
|
+
- ruby-await-node.gemspec
|
64
|
+
- ruby-await-node.js
|
65
|
+
- spec/fork_test_harness.rb
|
66
|
+
- spec/ruby_await_node_spec.rb
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
homepage: https://github.com/remitano/ruby-await-node
|
69
|
+
licenses:
|
70
|
+
- MIT
|
71
|
+
metadata: {}
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 2.4.5.2
|
89
|
+
signing_key:
|
90
|
+
specification_version: 4
|
91
|
+
summary: Efficiently execute js from ruby by keeping a long run nodejs process
|
92
|
+
test_files: []
|