Fri 4 Jan 2008
Thanks to a little help from the JRuby user mailing list, I learned that the behavior we had been seeing is not a bug. Local variables that come from an eval are created in ThreadLocal storage. I think I already should have know this as I was not having similar issues with global variables.
But in order to keep multiple users from interfering with each other and never being able to use local variables, I changed our RubyConsole object to run on its own thread. It now implements the HttpSessionBindingListener interface so that I can properly cleanup when the session expires. So there is happiness in JRuby integration land again.
I also learned how to capture the stdout and stderr from the JRuby environment and stuff them into ByteArrayOutputStream instances so that I can capture the data and put it on the HTML response along with the toString of any actual RubyObject that comes back from the eval call. So our JRuby console over AJAX HTTP looks a lot more like what happens in irb on a local terminal. This isn’t currently possible to due with pure BSF, but with a little help from JRuby objects it works by setting up like this when a new BSFManager is created.
protected ByteArrayOutputStream stdout = new ByteArrayOutputStream();
Ruby runtime = Ruby.getDefaultInstance();
IRubyObject out = new RubyIO( runtime, stdout );
manager.declareBean("stdout", out, RubyIO.class);
manager.declareBean("defout", out, RubyIO.class);
manager.declareBean(">", out, RubyIO.class);
And then after every eval call, we can get any available stdout data by calling
stdout.toString();
stdout.reset();
There are some other nice ways to do this without BSF and I still need to investigate whether there are improvements in JSR233 in JDK6 that would make this easier. But it works really nicely. Way to go JRuby guys!