JRuby 1.3.0 will include Nailgun


#1

Nailgun is a tool that speeds up Java command startup by punting
commands to an always-running “server” JVM. The improvement for JRuby is
substantial, with bare-bones startup times improving tenfold.

There’s work to be done, like to get signals to forward from the client
to the server and to more easily manage server instances, but it’s a
good start. We’re looking for people to try it out (to be released in
RC2 soon) and give us feedback.

Here’s a short sample session using JRuby with Nailgun:

~/projects/jruby âž” cd tool/nailgun/ ; make ; cd -
Building ng client. To build a Windows binary, type ‘make ng.exe’
gcc -Wall -pedantic -s -O3 -o ng src/c/ng.c
ld warning: option -s is obsolete and being ignored
/Users/headius/projects/jruby

~/projects/jruby âž” jruby --ng-server
NGServer started on all interfaces, port 2113.
^Z
[1]+ Stopped jruby --ng-server

~/projects/jruby âž” bg
[1]+ jruby --ng-server &

~/projects/jruby âž” jruby --ng -e “puts 1”
1

~/projects/jruby âž” time jruby -e “puts 1”
1

real 0m0.609s
user 0m0.482s
sys 0m0.119s

~/projects/jruby âž” time jruby --ng -e “puts 1”
1

real 0m0.073s
user 0m0.010s
sys 0m0.018s

  • Charlie

#2

On Thu, 14 May 2009 06:45:25 +0900, Charles Oliver N. wrote:

Nailgun is a tool that speeds up Java command startup by punting
commands to an always-running “server” JVM. The improvement for JRuby
is substantial, with bare-bones startup times improving tenfold.

This is so cool. I’ve been wanting something like it forever.

Is the nailgun implementation similar in concept to perperl (or
PersistentPerl, http://daemoninc.com/PersistentPerl/)? Is there anything
like it for MRI as well? In your opinion, would it be feasible?

Kudos!

David


#3

David P. wrote:

This is so cool. I’ve been wanting something like it forever.

Is the nailgun implementation similar in concept to perperl (or
PersistentPerl, http://daemoninc.com/PersistentPerl/)? Is there anything
like it for MRI as well? In your opinion, would it be feasible?

It would be wonderful if it could have all the Rails Active* libraries
preloaded; that would drastically shorten the startup time for unit
tests etc.

I don’t see in principle why it couldn’t be done - I guess you just fork
the process when a request comes in. The messy bit is passing the
stdin/stdout/stderr file descriptors over a socket. And it could be
called “railgun” :slight_smile:


#4

Unfortunately, the name ‘railgun’ has been taken by a games library :frowning:


#5

Brian C. wrote:

I don’t see in principle why it couldn’t be done - I guess you just fork
the process when a request comes in. The messy bit is passing the
stdin/stdout/stderr file descriptors over a socket. And it could be
called “railgun” :slight_smile:

It turns out all the messy passing of IO objects is already included in
the Ruby standard socket library, so without further ado, here’s a
working proof-of-concept :slight_smile: Tested under ruby 1.8.6p111

$ cat hello.rb

Silly program which requires active_support to work

puts ‘’.blank?

$ time ruby -rubygems -e’require “active_support”’ -e ‘load “hello.rb”’
true

real 0m1.522s
user 0m1.280s
sys 0m0.216s

$ ruby -rubygems -railgun -e’require “active_support”’
Railgun PID 10272 started

[in another console]
$ time ./rg 10272 hello.rb
true

real 0m0.076s
user 0m0.036s
sys 0m0.012s

---- 8< ---- ailgun.rb ----

Sample usage:

ruby -rubygems -railgun -e’require “active_support”’

Note that at least one ‘-e’ option is required, e.g. -e0

at_exit do
require ‘socket’
sockname = “/tmp/railgun#{$$}”
File.delete(sockname) rescue nil
trap(‘INT’) { File.delete(sockname) rescue nil; exit! }
server = UNIXServer.open(sockname)
puts “Railgun PID #{$$} started”
while client = server.accept
fork do
begin
STDIN.reopen(client.recv_io)
STDOUT.reopen(client.recv_io)
STDERR.reopen(client.recv_io)
# [script, args] # TODO: interpret flags like -I and -r
nbytes = client.read(4).unpack(“N”).first
args = Marshal.load(client.read(nbytes))
client.close
cmd = args.shift
ARGV.replace(args)
load cmd
rescue Exception => e
STDERR.puts e
client.close rescue nil
end
end
client.close
end
end
---- 8< ----

---- 8< ---- rg.rb ----
#!/usr/bin/env ruby

Usage: ruby rg.rb <args…>

require ‘socket’
pid = ARGV.shift
server = UNIXSocket.open("/tmp/railgun#{pid}")
args = Marshal.dump(ARGV)
server.send_io(STDIN)
server.send_io(STDOUT)
server.send_io(STDERR)
server.write [args.size].pack(“N”)
server.write args
---- 8< ----

The awkward thing from a user point of view is that you have to remember
the pid of where the railgun server is. You could use a fixed name, but
that would only let you have a single railgun process preloaded with a
single set of libraries.

Perhaps it would be better for the railgun server to drop you into a new
shell which has something in the ENV with this information.


#6

It turns out all the messy passing of IO objects is already included in
the Ruby standard socket library, so without further ado, here’s a
working proof-of-concept :slight_smile: Tested under ruby 1.8.6p111

Wow. You got me all excited now!

:slight_smile:


#7

David P. wrote:

Wow. You got me all excited now!

I’ve tidied it up a little and pushed the code to
http://github.com/candlerb/snailgun/tree/master

However, don’t get too excited - it doesn’t play particularly well with
Rails yet. In particular, “frake test:units” doesn’t run any tests. I’m
afraid I don’t have time to investigate further.

Regards,

Brian.


#8

Brian C. wrote:

Brian C. wrote:

I don’t see in principle why it couldn’t be done - I guess you just fork
the process when a request comes in. The messy bit is passing the
stdin/stdout/stderr file descriptors over a socket. And it could be
called “railgun” :slight_smile:

It turns out all the messy passing of IO objects is already included in
the Ruby standard socket library, so without further ado, here’s a
working proof-of-concept :slight_smile: Tested under ruby 1.8.6p111

This is essentially the magic behind Passenger, which also manages the
child processes and balances requests across them. So yeah, it’s doable
and not too peculiar.

  • Charlie

#9

Brian C. wrote:

However, don’t get too excited - it doesn’t play particularly well with
Rails yet. In particular, “frake test:units” doesn’t run any tests.

I’ve fixed that, and a number of other issues, and now it actually plays
rather well with Rails.

The new code detects that you are using rails and start multiple
processes, one for each environment of interest (by default ‘test’ and
‘development’), so that you can have a near-instant startup for either
purpose.