execjs-fastnode 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 13794e0f836dc1cc7c7eb16a23391df25a556e99
4
- data.tar.gz: 66e77b96d13a3bd2be1fd42e249f89e9c832aafb
3
+ metadata.gz: f5fe8728df68607be57c58f5207618c5e19d967f
4
+ data.tar.gz: 36f90d80531ea43e2a7c5ef9686d2459c081bf1b
5
5
  SHA512:
6
- metadata.gz: 017acad49287c35d0cf4c573939ddbdad3af1e55413d041ec024a71d76b7444286fa9794515fa18b395934e7f75e033100b393b52d3ade132b2e9ec1c85f8b0e
7
- data.tar.gz: af736abba04aeb15cae84a798f2556d9ebb052ae12c76329de34e2c3f65073c26bec5f4e9616a0420b8cb3cd109ffe6e73c0b8e2560f01bcdc15a26084d13f8f
6
+ metadata.gz: f55e3aba398c6221eed47a33ffb15829808f974172e144ce82a6168bead521e5cae15e71f76b8313603cba894ff89c017ffdd7b4f01676b532a7848e0c993a36
7
+ data.tar.gz: a08c714d6f3c56a55aaac58657c8d01645c42f3271af1ee4d1973363a587de3a2811abe4218fca502b6a9377d9072c38ecca3693a0a9d379fd280dfb72d7acc3
@@ -3,11 +3,23 @@ cache: bundler
3
3
  sudo: false
4
4
  before_script:
5
5
  - node --version
6
- matrix:
7
- include:
8
- - rvm: 2.0.0
9
- - rvm: 2.1
10
- - rvm: 2.2
11
- - rvm: 2.3.1
12
- - rvm: jruby-19mode
13
- - os: osx
6
+ env:
7
+ global:
8
+ EXECJS_RUNTIME: FastNode
9
+ matrix:
10
+ - NODE_VERSION=v0.10
11
+ - NODE_VERSION=v0.12
12
+ - NODE_VERSION=v4
13
+ - NODE_VERSION=v6
14
+ rvm:
15
+ - 2.0.0
16
+ - 2.3.1
17
+ - jruby-head
18
+ before_install:
19
+ - gem install bundler
20
+ - gem update bundler
21
+ - . $HOME/.nvm/nvm.sh
22
+ - nvm install $NODE_VERSION
23
+ os:
24
+ - osx
25
+ - linux
data/Gemfile CHANGED
@@ -1,6 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
-
4
- gem 'therubyracer'
3
+ gem 'therubyracer', platforms: :mri
5
4
 
6
5
  gemspec
File without changes
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # ExecJS FastNode
2
2
 
3
+ [![Build Status](https://travis-ci.org/jhawthorn/execjs-fastnode.svg?branch=master)](https://travis-ci.org/jhawthorn/execjs-fastnode)
4
+
3
5
  ## What's this?
4
6
 
5
7
  An alternative implementation of ExecJS's Node.js integration. This aims to speed up sprockets compilation without needing to embed a javascript interpreter inside of ruby like [therubyracer](cowboyd/therubyracer).
@@ -11,10 +13,10 @@ Much.
11
13
  ```
12
14
  $ rake bench
13
15
  ...
14
- user system total real
15
- Node.js (V8) fast 0.000000 0.000000 0.000000 ( 0.069723)
16
- therubyracer (V8) 0.020000 0.000000 0.020000 ( 0.018010)
17
- Node.js (V8) 0.000000 0.010000 1.470000 ( 1.487579)
16
+ user system total real
17
+ Node.js (V8) fast 0.000000 0.000000 0.000000 (0.069723)
18
+ therubyracer (V8) 0.020000 0.000000 0.020000 (0.018010)
19
+ Node.js (V8) 0.000000 0.010000 1.470000 (1.487579)
18
20
  ```
19
21
 
20
22
  Okay, so it's not as fast as `therubyracer`, but it's 20x faster than the standard ExecJS Node.js implementation.
@@ -25,17 +27,17 @@ The benchmark measures the time to compile the javascript CoffeeScript compiler
25
27
 
26
28
  The existing ExecJS runtime has to run a new Node.js process each time any JS is to be run. This means that if you are loading up the CoffeeScript compiler in order to compile some sprockets assets, it needs to reload the node executable and the entire coffeescript compiler for each file it is compiling.
27
29
 
28
- This implementation avoids this by starting an always running Node.js process connected by pipes to STDIN and STDOUT. The JS to execute is piped into the process and the results are recieved from its output. Isolation between different ExecJS contexts is achieved through Node's [vm.Script](https://nodejs.org/api/vm.html).
30
+ This implementation avoids this by starting a persistently running Node.js process connected through a UNIX socket. Isolation between different ExecJS contexts is achieved through Node's [vm.Script](https://nodejs.org/api/vm.html).
29
31
 
30
- | Old | New |
32
+ | ExecJS FastNode | Standard ExecJS Node |
31
33
  | --- | --- |
32
- | ![](docs/example_old.png) | ![](docs/example_new.png) |
34
+ | ![](docs/example_new.png) | ![](docs/example_old.png) |
33
35
 
34
36
  ## Is this production ready?
35
37
 
36
38
  Maybe? It needs more testing to be labeled as such. If you encounter any troubles please [file an issue](https://github.com/jhawthorn/execjs-fastnode/issues/new).
37
39
 
38
- Currently minimal effort is made to handle catastrophic errors: Node.js crashing, running out of memory, being killed. All of which result in `Errno::EPIPE: Broken pipe` for future ExecJS calls.
40
+ Currently minimal effort is made to handle catastrophic errors: Node.js crashing, running out of memory, being killed.
39
41
 
40
42
  It's probably fine for development.
41
43
 
@@ -78,9 +80,14 @@ I haven't seen any benchmarks or bug reports that demonstrate this, so I conside
78
80
 
79
81
  [mini_racer](https://github.com/discourse/mini_racer) is another option.
80
82
 
81
- The ExecJS Node runtime has its benefits as well. It should works on jRuby and other non-MRI runtimes.
83
+ The ExecJS Node runtime has its benefits as well. It should work on jRuby and other non-MRI runtimes.
82
84
  If this were merged upstream it would give developers fast javascript execution without needing an extra gem or configuration, just a working `node` somewhere in `$PATH`.
83
85
 
86
+ ## Limitations
87
+
88
+ * Syntax errors on Node.js versions 0.10.x and 4.x can't report the exact line number of the error. Instead they will always display `(execjs):1`.
89
+ * This only runs a single node process, so javascript execution can only make use of a single processor. This isn't much of an issue because sprockets doesn't parallelize builds. This runtime is so much faster than the original that this would only be an issue with very specific use cases.
90
+
84
91
  ## Development
85
92
 
86
93
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -91,8 +98,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
91
98
 
92
99
  Bug reports and pull requests are welcome on GitHub at https://github.com/jhawthorn/execjs-fastnode.
93
100
 
94
-
95
101
  ## License
96
102
 
97
103
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
98
-
@@ -1,28 +1,70 @@
1
+ require "net/http"
2
+ require "socket"
3
+ require "thread"
1
4
  require "tmpdir"
2
5
  require 'json'
3
- require "open3"
4
- require "thread"
5
6
  require "execjs/runtime"
6
7
  require "execjs/fastnode/command"
7
8
 
8
9
  module ExecJS
9
10
  module FastNode
10
11
  class ExternalPipedRuntime < ExecJS::Runtime
11
- class VM
12
- attr_reader :stdin, :stdout
12
+ class VMCommand
13
+ def initialize(socket_path, cmd, arguments)
14
+ @socket_path = socket_path.to_s
15
+ @cmd = cmd
16
+ @arguments = arguments
17
+ end
18
+
19
+ def execute
20
+ sock = Net::BufferedIO.new(socket)
21
+
22
+ request = Net::HTTP::Post.new("/")
23
+ request['Connection'] = 'close'
24
+ request['Content-Type'] = 'application/json'
25
+ request.body = contents
26
+ request.exec(sock, "1.1", "/")
27
+
28
+ begin
29
+ response = Net::HTTPResponse.read_new(sock)
30
+ end while response.kind_of?(Net::HTTPContinue)
31
+
32
+ response.reading_body(sock, request.response_body_permitted?) { }
33
+ sock.close
34
+
35
+ parse(response.body)
36
+ end
37
+
38
+ private
39
+
40
+ def socket
41
+ UNIXSocket.new(@socket_path)
42
+ end
43
+
44
+ def contents
45
+ ::JSON.generate({cmd: @cmd, args: @arguments})
46
+ end
47
+
48
+ def parse(body)
49
+ ::JSON.parse(body, create_additions: false)
50
+ end
51
+ end
13
52
 
53
+ class VM
14
54
  def initialize(options)
15
55
  @mutex = Mutex.new
16
- @stdin = @stdout = @wait_thr = nil
56
+ @socket_path = nil
17
57
  @options = options
18
58
  end
19
59
 
20
60
  def started?
21
- !!@stdin
61
+ !!@socket_path
22
62
  end
23
63
 
24
- def self.finalize(stdin)
25
- proc { stdin.puts('{"cmd": "exit", "arguments": [0]}') }
64
+ def self.finalize(socket_path)
65
+ proc {
66
+ VMCommand.new(socket_path, "exit", [0]).execute
67
+ }
26
68
  end
27
69
 
28
70
  def exec(context, source)
@@ -34,9 +76,7 @@ module ExecJS
34
76
  end
35
77
 
36
78
  def start
37
- return if started?
38
79
  @mutex.synchronize do
39
- # will double check started? with the lock held
40
80
  start_without_synchronization
41
81
  end
42
82
  end
@@ -45,15 +85,27 @@ module ExecJS
45
85
 
46
86
  def start_without_synchronization
47
87
  return if started?
48
- @stdin, @stdout, @wait_thr = Open3.popen2(@options[:binary], @options[:runner_path])
49
- ObjectSpace.define_finalizer(self, self.class.finalize(@stdin))
88
+ dir = Dir.mktmpdir("execjs-fastnode-")
89
+ @socket_path = File.join(dir, "socket")
90
+ @pid = Process.spawn({"PORT" => @socket_path.to_s}, @options[:binary], @options[:runner_path])
91
+
92
+ retries = 20
93
+ while !File.exists?(@socket_path)
94
+ sleep 0.05
95
+ retries -= 1
96
+
97
+ if retries == 0
98
+ raise "Unable to start nodejs process"
99
+ end
100
+ end
101
+
102
+ ObjectSpace.define_finalizer(self, self.class.finalize(@socket_path))
50
103
  end
51
104
 
52
105
  def command(cmd, *arguments)
53
- start
54
106
  @mutex.synchronize do
55
- @stdin.puts(::JSON.generate({cmd: cmd, args: arguments}))
56
- result = ::JSON.parse(@stdout.gets, create_additions: false)
107
+ start_without_synchronization
108
+ VMCommand.new(@socket_path, cmd, arguments).execute
57
109
  end
58
110
  end
59
111
  end
@@ -126,18 +178,16 @@ module ExecJS
126
178
  @deprecated = !!options[:deprecated]
127
179
  @binary = nil
128
180
 
181
+ @vm = VM.new(
182
+ binary: binary,
183
+ runner_path: @runner_path
184
+ )
185
+
129
186
  @popen_options = {}
130
187
  @popen_options[:external_encoding] = @encoding if @encoding
131
188
  @popen_options[:internal_encoding] = ::Encoding.default_internal || 'UTF-8'
132
189
  end
133
190
 
134
- def vm
135
- @vm ||= VM.new(
136
- binary: @binary,
137
- runner_path: @runner_path
138
- )
139
- end
140
-
141
191
  def available?
142
192
  binary ? true : false
143
193
  end
@@ -1,72 +1,84 @@
1
- (function(){
2
- var stdin = process.stdin
3
- var stdout = process.stdout
4
- var buf = ""
1
+ var stdin = process.stdin
2
+ var stdout = process.stdout
3
+ var buf = ""
5
4
 
6
- stdin.setEncoding('utf8')
5
+ stdin.setEncoding('utf8')
7
6
 
8
- var vm = require('vm');
9
- var contexts = {};
7
+ var vm = require('vm');
8
+ var contexts = {};
10
9
 
11
- function getContext(uuid) {
12
- return contexts[uuid] || (contexts[uuid] = vm.createContext())
10
+ /*
11
+ * Versions of node before 0.12 (notably 0.10) didn't properly propagate
12
+ * syntax errors.
13
+ * This also regressed in the node 4.0 releases.
14
+ *
15
+ * To get around this, if it looks like we are missing the location of the
16
+ * error, we guess it is (execjs):1
17
+ *
18
+ * This is obviously not ideal, but only affects syntax errors, and only on
19
+ * these versions.
20
+ */
21
+ function massageStackTrace(stack) {
22
+ if (stack && stack.indexOf("SyntaxError") == 0) {
23
+ return "(execjs):1\n" + stack;
24
+ } else {
25
+ return stack;
13
26
  }
27
+ }
14
28
 
15
- var commands = {
16
- deleteContext: function(uuid) {
17
- delete contexts[uuid];
18
- return 1;
19
- },
20
- exit: function(code) {
21
- process.exit(code)
22
- },
23
- exec: function execJS(input) {
24
- var context = getContext(input.context);
25
- var source = input.source;
26
- try {
27
- var program = function(){
28
- return vm.runInContext(source, context, "(execjs)");
29
- }
30
- result = program();
31
- if (typeof result == 'undefined' && result !== null) {
32
- return ['ok'];
33
- } else {
34
- try {
35
- return ['ok', result];
36
- } catch (err) {
37
- return ['err', '' + err, err.stack];
38
- }
29
+ function getContext(uuid) {
30
+ return contexts[uuid] || (contexts[uuid] = vm.createContext())
31
+ }
32
+
33
+ var commands = {
34
+ deleteContext: function(uuid) {
35
+ delete contexts[uuid];
36
+ return 1;
37
+ },
38
+ exit: function(code) {
39
+ process.exit(code)
40
+ },
41
+ exec: function execJS(input) {
42
+ var context = getContext(input.context);
43
+ var source = input.source;
44
+ try {
45
+ var program = function(){
46
+ return vm.runInContext(source, context, "(execjs)");
47
+ }
48
+ result = program();
49
+ if (typeof result == 'undefined' && result !== null) {
50
+ return ['ok'];
51
+ } else {
52
+ try {
53
+ return ['ok', result];
54
+ } catch (err) {
55
+ return ['err', '' + err, err.stack];
39
56
  }
40
- } catch (err) {
41
- return ['err', '' + err, err.stack];
42
57
  }
58
+ } catch (err) {
59
+ return ['err', '' + err, massageStackTrace(err.stack)];
43
60
  }
44
61
  }
62
+ }
45
63
 
46
- function processLine(line){
47
- var input = JSON.parse(line)
48
- var result = commands[input.cmd].apply(null, input.args)
64
+ var http = require('http')
65
+ var server = http.createServer(function(req, res) {
66
+ var contents = '';
49
67
 
50
- var outputJSON = JSON.stringify(result)
51
- stdout.write(outputJSON)
52
- stdout.write('\n')
53
- }
68
+ req.on('data', function (dataIn) {
69
+ contents += dataIn;
70
+ });
54
71
 
55
- function processBuffer(){
56
- if(buf.indexOf('\n') >= 0){
57
- var lines = buf.split('\n')
58
- for(var i = 0; i < lines.length - 1; i++) {
59
- processLine(lines[i]);
60
- }
61
- buf = lines[lines.length - 1]
62
- }
63
- }
72
+ req.on('end', function () {
73
+ var input = JSON.parse(contents)
74
+ var result = commands[input.cmd].apply(null, input.args)
75
+ var outputJSON = JSON.stringify(result)
64
76
 
65
- process.stdin.on('readable', function(){
66
- var chunk = process.stdin.read();
67
- if (chunk !== null) {
68
- buf += chunk
69
- processBuffer()
70
- }
77
+ res.statusCode = 200;
78
+ res.setHeader('Content-Type', 'application/json');
79
+ res.end(outputJSON);
71
80
  });
72
- })();
81
+ });
82
+
83
+ var port = process.env.PORT || 3001;
84
+ server.listen(port);
@@ -1,5 +1,5 @@
1
1
  module ExecJS
2
2
  module FastNode
3
- VERSION = "0.1.0"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: execjs-fastnode
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Hawthorn
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-10-25 00:00:00.000000000 Z
11
+ date: 2016-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: execjs
@@ -77,7 +77,7 @@ files:
77
77
  - ".gitmodules"
78
78
  - ".travis.yml"
79
79
  - Gemfile
80
- - LICENSE.txt
80
+ - LICENSE
81
81
  - README.md
82
82
  - Rakefile
83
83
  - bin/console