[ANN] JRuby 1.3.0 will include Nailgun

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

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

···

On Thu, 14 May 2009 06:45:25 +0900, Charles Oliver Nutter 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.

David Palm 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:

···

--
Posted via http://www.ruby-forum.com/\.

Brian Candler 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 <pid> <cmd> <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.

···

--
Posted via http://www.ruby-forum.com/\.

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

···

--
Posted via http://www.ruby-forum.com/.

Brian Candler wrote:

Brian Candler 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

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:

David Palm wrote:

Wow. You got me all excited now!

I've tidied it up a little and pushed the code to

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.

···

--
Posted via http://www.ruby-forum.com/\.

Brian Candler 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.

···

--
Posted via http://www.ruby-forum.com/\.