Fixing JRuby

JRuby

While trying to figure out a problem with the scaling, I was getting a bunch of BindExceptions and they seemed to point to this point in the jruby-1.7.0 code:

  1. try {
  2. // This is a bit convoluted because (1) SocketChannel.bind
  3. // is only in jdk 7 and (2) Socket.getChannel() seems to
  4. // return null in some cases
  5. channel = SocketChannel.open();
  6. Socket socket = channel.socket();
  7.  
  8. if (localHost != null) {
  9. socket.bind( new InetSocketAddress(
  10. InetAddress.getByName(localHost),
  11. localPort) );
  12. }
  13.  
  14. try {
  15. // Do this nonblocking so we can be interrupted
  16. channel.configureBlocking(false);
  17. channel.connect( new InetSocketAddress(
  18. InetAddress.getByName(remoteHost),
  19. remotePort) );
  20. context.getThread().select(channel, this, SelectionKey.OP_CONNECT);
  21. channel.finishConnect();
  22.  
  23. // only try to set blocking back if we succeeded to finish connecting
  24. channel.configureBlocking(true);

If I was getting bind exceptions, then it had to be coming from line 104 - after all, that's the call to bind(). The problem was, this really needed to be preceded with a call to set SO_REUSEADDR to true so that we didn't have these kinds of issues.

Something like this:

  1. try {
  2. // This is a bit convoluted because (1) SocketChannel.bind
  3. // is only in jdk 7 and (2) Socket.getChannel() seems to
  4. // return null in some cases
  5. channel = SocketChannel.open();
  6. Socket socket = channel.socket();
  7.  
  8. if (localHost != null) {
  9. socket.setReuseAddress(true);
  10. socket.bind( new InetSocketAddress(
  11. InetAddress.getByName(localHost),
  12. localPort) );
  13. }
  14.  
  15. try {
  16. // Do this nonblocking so we can be interrupted
  17. channel.configureBlocking(false);
  18. channel.connect( new InetSocketAddress(
  19. InetAddress.getByName(remoteHost),
  20. remotePort) );
  21. context.getThread().select(channel, this, SelectionKey.OP_CONNECT);
  22. channel.finishConnect();
  23.  
  24. // only try to set blocking back if we succeeded to finish connecting
  25. channel.configureBlocking(true);

Simple change, but it'd make a huge difference to the operation of the socket. So I submitted a request to the JRuby team.

After a little bit, I realized that I needed to try and build this all myself, and see if I couldn't just prove the problem for myself. The difficulty was - How to build JRuby?

Turns out, it's not all that hard. First, fork the code on GitHub so you can issue Pull Requests with the changed code. Then, follow the directions to build everything about JRuby:

  $ git clone git://github.com/drbobbeaty/jruby.git
  $ cd jruby
  $ ant
  $ ant jar-complete
  $ ant dist

It should all build just fine. Then check everything in, and push it up to GitHub. Next, you need to tell rvm to build a new version of ruby from that GitHub repo:

  $ rvm get head
  $ rvm reinstall jruby-head --url git://github.com/

And we're almost doneā€¦ Update the Gemfile to have the paths to the jruby-jars file where you built it, and then update the .rvmrc. I was able to get things running and even packaging a jar file for our deployment.

Hacking on the language. That's a first for me. Quite a fun experience.

What I noticed was that it wasn't the bind on line 104 - it was the connect on line 113. Java returned the BindException as a general Can't get the endpoint, and I found this out by putting in the catch statements and logging the host and port of the exception. This was very illuminating, and I'm very glad I did it.

I'm going to even send a pull request to see if they'll take it. It's a very useful debugging tool.