ruby-await-nodejs 0.0.4
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 +7 -0
- data/.circleci/config.yml +17 -0
- data/.gitignore +2 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +37 -0
- data/LICENSE +22 -0
- data/README.md +91 -0
- data/lib/ruby-await-nodejs/context.rb +83 -0
- data/lib/ruby-await-nodejs/exception.rb +4 -0
- data/lib/ruby-await-nodejs/runtime.rb +105 -0
- data/lib/ruby-await-nodejs/version.rb +3 -0
- data/lib/ruby-await-nodejs.rb +2 -0
- data/ruby-await-nodejs.gemspec +18 -0
- data/ruby-await-nodejs.js +69 -0
- data/spec/fork_test_harness.rb +35 -0
- data/spec/ruby_await_node_spec.rb +162 -0
- data/spec/sample_entry.js +1 -0
- data/spec/spec_helper.rb +2 -0
- metadata +91 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b011818d77969f2e0efdfdf27dee659cfa4d7d896f34181178c260135053ac81
|
4
|
+
data.tar.gz: 32aa42178b25ef2d007d36bc1521bbfa9c3ae889fbb35391df9c7e4ca09fde35
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3f5ae612ab8609130e3733c2bcfe66926f36daaaa1f023578f487d84b11a078f45681190e5b9431299b133e8a9ae2cd04f07cc54f34f42945c95e9a81c11f8c6
|
7
|
+
data.tar.gz: 2af21e88750990aaaa2aad0b4c85325e3aadb4253dbf54a41da3d97eae94517f2dc5c7175eb0d848e2dce0905abb4031b044ac69537cd97890b7221e019f50d2
|
@@ -0,0 +1,17 @@
|
|
1
|
+
version: 2
|
2
|
+
template:
|
3
|
+
docker: &docker
|
4
|
+
- image: cimg/ruby:2.6.5-node
|
5
|
+
jobs:
|
6
|
+
test:
|
7
|
+
docker: *docker
|
8
|
+
steps:
|
9
|
+
- checkout
|
10
|
+
- run: gem install bundler:1.16.1
|
11
|
+
- run: bundle
|
12
|
+
- run: bundle exec rspec spec/
|
13
|
+
workflows:
|
14
|
+
version: 2
|
15
|
+
test:
|
16
|
+
jobs:
|
17
|
+
- test
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
ruby-await-nodejs (0.0.4)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
byebug (11.1.3)
|
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 (~> 11.1.3)
|
30
|
+
rspec
|
31
|
+
ruby-await-nodejs!
|
32
|
+
|
33
|
+
RUBY VERSION
|
34
|
+
ruby 3.1.2p20
|
35
|
+
|
36
|
+
BUNDLED WITH
|
37
|
+
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,91 @@
|
|
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
|
+
- [x] Execute await/async method
|
25
|
+
|
26
|
+
# Getting Started
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
gem 'ruby-await-node'
|
30
|
+
```
|
31
|
+
|
32
|
+
Or if you want to run the latest version
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
gem 'ruby-await-node', :git => 'git@github.com:remitano/ruby-await-node.git'
|
36
|
+
```
|
37
|
+
|
38
|
+
Then to invoke some javascript
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
runtime = RubyAwaitNode::Runtime.new(debug: true)
|
42
|
+
context = RubyAwaitNode::Context.new(runtime)
|
43
|
+
entry_path = File.expand_path("entry.js", __dir__)
|
44
|
+
context.eval("global.actor = require(#{entry_path.to_json})")
|
45
|
+
result = context.eval("actor.method()")
|
46
|
+
```
|
47
|
+
|
48
|
+
Your entry.js may look like this:
|
49
|
+
```javascript
|
50
|
+
const moment = require("moment")
|
51
|
+
module.exports = {
|
52
|
+
method: function() {
|
53
|
+
return something;
|
54
|
+
},
|
55
|
+
|
56
|
+
asyncMethod: async function() {
|
57
|
+
await operation;
|
58
|
+
return something;
|
59
|
+
}
|
60
|
+
}
|
61
|
+
```
|
62
|
+
|
63
|
+
Under the hood ruby-await-node will automatically wait for async method
|
64
|
+
to complete
|
65
|
+
|
66
|
+
## Examples
|
67
|
+
|
68
|
+
### Load a javascript file
|
69
|
+
```
|
70
|
+
context.load(File.expand_path("entry.js", __dir__))
|
71
|
+
```
|
72
|
+
|
73
|
+
*Warning*: always pass the absolute path
|
74
|
+
### Simple execution
|
75
|
+
```
|
76
|
+
context.eval("1.0 + 2.0")
|
77
|
+
context.eval("global.a = 1.0")
|
78
|
+
```
|
79
|
+
|
80
|
+
### Calling function with arguments
|
81
|
+
```
|
82
|
+
context.call("(function(b) { return global.a * b })", 5)
|
83
|
+
```
|
84
|
+
|
85
|
+
This will be equivalent to execute this in nodejs
|
86
|
+
```
|
87
|
+
(function(b) { return a * b }).apply(this, [5])
|
88
|
+
```
|
89
|
+
|
90
|
+
### Other examples
|
91
|
+
Look into spec/ for other potential examples
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'socket'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'json'
|
5
|
+
require_relative "exception"
|
6
|
+
|
7
|
+
module RubyAwaitNodejs
|
8
|
+
class Context
|
9
|
+
# runtime is an instance of RubyAwaitNodejs
|
10
|
+
# src is the js code to be eval()'d in the nodejs context
|
11
|
+
def initialize(runtime, src = "", options = {})
|
12
|
+
@runtime = runtime
|
13
|
+
@execution_timeout = options[:execution_timeout] || 60 # seconds
|
14
|
+
|
15
|
+
# compile context source, in most cases
|
16
|
+
# this is something like the CoffeeScript compiler
|
17
|
+
# or the SASS compiler
|
18
|
+
eval(src)
|
19
|
+
end
|
20
|
+
|
21
|
+
def eval(src)
|
22
|
+
if /\S/ =~ src #IMPORTANT! /\S/ =~ "()" => 0
|
23
|
+
exec(src)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def load(path)
|
28
|
+
exec("require(#{path.to_json})")
|
29
|
+
end
|
30
|
+
|
31
|
+
def exec(src)
|
32
|
+
return "" unless src.length > 0
|
33
|
+
|
34
|
+
src = src.encode('UTF-8', :undef => :replace, :replace => '')
|
35
|
+
src = compile_source(src)
|
36
|
+
|
37
|
+
# src is either an empty object
|
38
|
+
# OR a valid JSON string in the form
|
39
|
+
# ['ok', 'result-of-coffeescript-or-sass-compiler']
|
40
|
+
# OR if an error occured
|
41
|
+
# ['err', 'some sort of error to be presented to the developer as a sprockets error']
|
42
|
+
#
|
43
|
+
status, value = src.empty? ? [] : ::JSON.parse(src, create_additions: false)
|
44
|
+
if status == "ok"
|
45
|
+
value
|
46
|
+
elsif value =~ /SyntaxError:/
|
47
|
+
raise RuntimeError, value
|
48
|
+
else
|
49
|
+
raise ProgramError, value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def call(identifier, *args)
|
54
|
+
eval "#{identifier}.apply(this, #{::JSON.generate(args)})"
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def compile_source(contents)
|
60
|
+
sock = Net::BufferedIO.new(
|
61
|
+
@runtime.provision_socket,
|
62
|
+
read_timeout: @execution_timeout,
|
63
|
+
write_timeout: @execution_timeout,
|
64
|
+
continue_timeout: @execution_timeout
|
65
|
+
)
|
66
|
+
request = Net::HTTP::Post.new("/")
|
67
|
+
request['Connection'] = 'close'
|
68
|
+
request['Content-Type'] = 'application/x-www-form-urlencoded'
|
69
|
+
# This Host header to make sure NodeJS does not throw ClientError, since change from version 23.x with new HTTP parser
|
70
|
+
request['Host'] = 'localhost'
|
71
|
+
request.body = contents
|
72
|
+
request.exec(sock, "1.1", "/")
|
73
|
+
|
74
|
+
begin
|
75
|
+
response = Net::HTTPResponse.read_new(sock)
|
76
|
+
end while response.kind_of?(Net::HTTPContinue)
|
77
|
+
|
78
|
+
response.reading_body(sock, request.response_body_permitted?) { }
|
79
|
+
sock.close
|
80
|
+
response.body
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
require_relative 'context'
|
4
|
+
|
5
|
+
module RubyAwaitNodejs
|
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
|
+
"RubyAwaitNodejs"
|
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
|
+
RubyAwaitNodejs::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-nodejs.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-nodejs")
|
76
|
+
path = tmpfile.path
|
77
|
+
tmpfile.close
|
78
|
+
tmpfile.unlink
|
79
|
+
path
|
80
|
+
end
|
81
|
+
|
82
|
+
ruby_await_nodejs_js_path = File.join(File.dirname(File.expand_path(__FILE__)), '../../ruby-await-nodejs.js')
|
83
|
+
command_options = [ruby_await_nodejs_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-nodejs/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'ruby-await-nodejs'
|
7
|
+
s.version = RubyAwaitNodejs::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-nodejs"
|
14
|
+
s.license = 'MIT'
|
15
|
+
|
16
|
+
s.add_development_dependency 'rspec'
|
17
|
+
s.add_development_dependency 'byebug', '~> 11.1.3'
|
18
|
+
end
|
@@ -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,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'byebug'
|
4
|
+
require 'ruby-await-nodejs'
|
5
|
+
|
6
|
+
ruby_await_node_runtime = RubyAwaitNodejs::Runtime.new(:debug => false)
|
7
|
+
context = RubyAwaitNodejs::Context.new(ruby_await_node_runtime)
|
8
|
+
context.eval("global.a = 2.0")
|
9
|
+
|
10
|
+
fork_count = 4
|
11
|
+
|
12
|
+
pids = (0...fork_count).map do |i|
|
13
|
+
fork do
|
14
|
+
sleep i * 0.1 # simulate different process exit at different time to expose premature shutdown of shared nodejs process
|
15
|
+
result = context.call("(function(b) { return (global.a * b); })", i)
|
16
|
+
exit result #use the exit code to communicate the calculation result
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
exit_statuses = pids.map { |pid| Process.wait2(pid) }
|
21
|
+
|
22
|
+
results = []
|
23
|
+
|
24
|
+
exit_statuses.each do |pid, status|
|
25
|
+
results << status.exitstatus
|
26
|
+
end
|
27
|
+
|
28
|
+
# http://www.wolframalpha.com/input/?i=3rd+trianglur+number
|
29
|
+
sum = results.compact.inject(0) { |result, element| result + element }
|
30
|
+
|
31
|
+
if (sum) == 12
|
32
|
+
exit 0
|
33
|
+
else
|
34
|
+
exit 1
|
35
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RubyAwaitNodejs 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
|
+
RubyAwaitNodejs::Runtime.new(:debug => false)
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_ruby_await_node_context(timeout: nil)
|
17
|
+
RubyAwaitNodejs::Context.new(create_ruby_await_node_runtime, "", { execution_timeout: timeout })
|
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
|
+
|
33
|
+
it "will raise error if the execution_timeout is less than actual execution time" do
|
34
|
+
expect do
|
35
|
+
create_ruby_await_node_context(timeout: 0.05).eval(await_return_42)
|
36
|
+
end.to raise_error(Net::ReadTimeout)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "load an entry file" do
|
41
|
+
it "loads correctly" do
|
42
|
+
path = File.expand_path("sample_entry.js", __dir__)
|
43
|
+
context = create_ruby_await_node_context
|
44
|
+
context.load(path)
|
45
|
+
js_result = context.eval("global.entryName")
|
46
|
+
expect(js_result).to eq "sample"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it "raises an RubyAwaitNodejs::ProgramError on error" do
|
51
|
+
expect {
|
52
|
+
create_ruby_await_node_context.eval("(function() { throw new Error('foo\\nbar', 0, 'test.js'); })()")
|
53
|
+
}.to raise_error(RubyAwaitNodejs::ProgramError)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "requires js to be in a self-executing function" do
|
57
|
+
js_result = -1
|
58
|
+
|
59
|
+
expect {
|
60
|
+
js_result = create_ruby_await_node_context.eval("return true;")
|
61
|
+
}.to raise_error(RubyAwaitNodejs::RuntimeError)
|
62
|
+
|
63
|
+
expect(js_result).to eq(-1)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "safely allows subsequent #eval calls after runtime error" do
|
67
|
+
js_result = create_ruby_await_node_context.eval(return_42)
|
68
|
+
expect(js_result).to eq 42
|
69
|
+
|
70
|
+
expect {
|
71
|
+
js_result = create_ruby_await_node_context.eval("return true;")
|
72
|
+
}.to raise_error(RubyAwaitNodejs::RuntimeError)
|
73
|
+
|
74
|
+
js_result = create_ruby_await_node_context.eval(return_42)
|
75
|
+
expect(js_result).to eq 42
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should be thread safe when sharing context between threads" do
|
79
|
+
thread_count = 128
|
80
|
+
threads = []
|
81
|
+
semaphore = Mutex.new
|
82
|
+
results = []
|
83
|
+
|
84
|
+
context = create_ruby_await_node_context
|
85
|
+
context.eval("global.a = 1.0;")
|
86
|
+
|
87
|
+
thread_count.times { |index|
|
88
|
+
threads << Thread.new {
|
89
|
+
result = context.call("(function(b) { return (global.a * b); })", index)
|
90
|
+
semaphore.synchronize {
|
91
|
+
results << result
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
threads.each { |t| t.join }
|
97
|
+
|
98
|
+
sum = results.inject(0) { |result, element| result + element }
|
99
|
+
|
100
|
+
expect(sum).to eq(8128) # http://www.wolframalpha.com/input/?i=127th+trianglur+number
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should be thread safe when creating new contexts in threads" do
|
104
|
+
thread_count = 128
|
105
|
+
threads = []
|
106
|
+
semaphore = Mutex.new
|
107
|
+
results = []
|
108
|
+
|
109
|
+
thread_count.times { |index|
|
110
|
+
threads << Thread.new {
|
111
|
+
context = create_ruby_await_node_context
|
112
|
+
context.eval("global.a = 2.0;") # this asserts that the context is shared
|
113
|
+
result = context.call("(function(b) { return (a * b); })", index)
|
114
|
+
semaphore.synchronize {
|
115
|
+
results << result
|
116
|
+
}
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
threads.each { |t| t.join }
|
121
|
+
|
122
|
+
sum = results.inject(0) { |result, element| result + element }
|
123
|
+
|
124
|
+
expect(sum).to eq(8128 * 2) # http://www.wolframalpha.com/input/?i=127th+trianglur+number
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should not break if exception occurs in thread" do
|
128
|
+
context_a = create_ruby_await_node_context
|
129
|
+
|
130
|
+
g_err_a = nil
|
131
|
+
g_err_b = nil
|
132
|
+
|
133
|
+
make_program_error_thread = Thread.new {
|
134
|
+
begin
|
135
|
+
_b = context_a.call("(function() { asd() })")
|
136
|
+
rescue => err_a
|
137
|
+
g_err_a = err_a
|
138
|
+
end
|
139
|
+
}
|
140
|
+
|
141
|
+
context_b = create_ruby_await_node_context
|
142
|
+
|
143
|
+
begin
|
144
|
+
_c = context_b.call("(function() { asd) })")
|
145
|
+
rescue => err_b
|
146
|
+
g_err_b = err_b
|
147
|
+
end
|
148
|
+
|
149
|
+
make_program_error_thread.join
|
150
|
+
|
151
|
+
expect(g_err_a).to be_kind_of(RubyAwaitNodejs::ProgramError)
|
152
|
+
expect(g_err_b).to be_kind_of(RubyAwaitNodejs::RuntimeError)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should work when the context is shared between forked processes" do
|
156
|
+
fork_test_harness_pid = Process.spawn("bundle exec ruby spec/fork_test_harness.rb")
|
157
|
+
exit_pid, exit_status = Process.wait2(fork_test_harness_pid)
|
158
|
+
|
159
|
+
expect(exit_pid).to eq(fork_test_harness_pid)
|
160
|
+
expect(exit_status).to eq(0)
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
global.entryName = "sample"
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-await-nodejs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
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: 2025-07-02 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: 11.1.3
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - "~>"
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 11.1.3
|
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
|
+
- Gemfile
|
54
|
+
- Gemfile.lock
|
55
|
+
- LICENSE
|
56
|
+
- README.md
|
57
|
+
- lib/ruby-await-nodejs.rb
|
58
|
+
- lib/ruby-await-nodejs/context.rb
|
59
|
+
- lib/ruby-await-nodejs/exception.rb
|
60
|
+
- lib/ruby-await-nodejs/runtime.rb
|
61
|
+
- lib/ruby-await-nodejs/version.rb
|
62
|
+
- ruby-await-nodejs.gemspec
|
63
|
+
- ruby-await-nodejs.js
|
64
|
+
- spec/fork_test_harness.rb
|
65
|
+
- spec/ruby_await_node_spec.rb
|
66
|
+
- spec/sample_entry.js
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
homepage: https://github.com/remitano/ruby-await-nodejs
|
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
|
+
rubygems_version: 3.3.7
|
88
|
+
signing_key:
|
89
|
+
specification_version: 4
|
90
|
+
summary: Efficiently execute js from ruby by keeping a long run nodejs process
|
91
|
+
test_files: []
|