Hacks and Monkey Patching with JRuby
If you’ve never had to do some hacking or monkey patching, then odds are you haven’t written or deployed a JRuby standalone or web application. Ok, that’s a bit dramatic but over the past year, while working with JRuby, I’ve had to implement quite a few patches. Today I’d like to share some of the issues I ran into and how I patched them.
What is Monkey Patching
Monkey patching, gorilla patching or duck patching all refer to the same process of extending or modifying code at run-time without modifying the original source. Dynamic languages, such as Ruby, make this particularly easy to accomplish. However, it’s not all sunshine and rose petals in monkey patch land. In fact, in a 2008 blog post by Avdi Grimm titled, Monkeypatching is Destroying Ruby, he called out some of the issues with it, such as sustainability.
Certainly a monkey patch can be useful or flat out necessary at times, but why? How do we get to a point where we have to rely on creating these wild patches in the first place?
Why Hack and Monkey Patch
As you might expect, there are all sorts of reasons why you might find the need to hack and patch. I’ve personally run into the following categories of issues which have led me to apply various patches over the last year:
- Bug in a gem
- Bug in JRuby
It’s probably worth mentioning that my work environment precludes me from downloading and using new releases of code (gems, platforms, etc) as easily/quickly as you’re probably used to when your at home (or at many other employers). These sorts of constraints are what lead me to hack and patch when necessary to get the job done, when I can’t simply fetch something from Github or Rubygems on a whim.
In the following sections, I’ll walk through examples that fall into one of the categories mentioned above.
Bug in a Gem
Developers all know there’s going to be bugs, it’s inevitable. I must admit I’ve been quite impressed with the amount of open source software that works “out of the box”. But there are times when we run into bugs or other issues that we need to get around.
Recently I ran into an issue with the mongo_session_store gem which provides Rails compatible session stores for MongoMapper and Mongoid. The specific problem I had was that
mongo_session_store was trying to load MongoMapper which I hadn’t previously installed. This immediately stood out to me because I was using Mongoid, so I wasn’t expecting MongoMapper to be required.
I should rewind a little bit to explain how I narrowed the issue down to mongo_session_store. In the logs I just followed the stacktrace of an exception which led me here. At this point I knew where the problem came from, but I wasn’t sure why. I dug a little bit more to see how the code was setup and being required in
mongo_session_store. That’s when I discovered the mongosessionstore-rails3 module, which makes use of Ruby’s autoload feature. Here’s the code for most of the module:
It appears that the second
if block (which checks for the
MongoMapperStore::Session) causes the
MongoMapperStore class to be loaded which attempts to
require 'mongo_mapper'. I’m using this gem in a JRuby on Rails application which gets deployed as a Java WAR) file to Apache Tomcat. This particular bug only seems to occur when the application is deployed to Tomcat. If I run the application using WEBrick, I’ve had no issues.
At this point, I’m not exactly sure what’s causing the
MongoMapperStore to get loaded when running in Tomcat vs. WEBRick (even when running with JRuby), which is why I opened the issue on Github. Since this issue was actually stopping my application from working, I had to make a quick hack to get things moving along while I wait for the gem author to ( hopefully ) reproduce and fix the issue.
I looked through the history of the
MongoSessionStore module and saw the autoloading was added a few months ago. I essentially removed the autoloading and added back the standard require’s wrapped in begin-rescue blocks. Once I had made these changes, I modified the gemspec so I could rebuild the gem with a slightly different name (to make it a little more obvious we’ll need to get an updated version of the original gem in the future). After which, I built the gem and uploaded it to a private gem repository used at work, and things worked just fine.
Bug in JRuby
I’ve been using JRuby for the past year beginning with version 1.5.6 and now I’m up to 1.6.5. In fact some of my recent monkey patches have been applied to resolve issues which were fixed in JRuby 1.6.6 (although I don’t have it available at work just yet).
One of the first things I do when I setup Tomcat to host my Rails applications is enable two-way SSL. You may be familiar with TLS/SSL (as used with HTTPS) in which a website presents a certificate to your browser. The browser checks to see if the certificate is valid (i.e. hasn’t expired, signed by a trusted CA and a few other things) and then finishes setting up the HTTPS connection (unless the certificate is invalid).
However, two-way SSL (also known as mutual authentication) takes it a step further. With two-way SSL the server presents a certificate but it also requires the user to present a certificate as well. All of the users of the Rails applications I develop have their own personal certificate as well.
In early 2011 I was all excited when I was ready to deploy my first Ruby on Rails application as a WAR to a Tomcat server (configured with two-way SSL). I fired up the server, tailed the logs, browsed to the site and BAM!
I hit up google and stumbled across JRUBY-5529 which was scheduled to be fixed in JRuby 1.6.6 (not available at the time). However, it wasn’t all bad news as someone (named Karl Baum) had posted a gist to monkey patch the
One of the great things about JRuby is that you can do just about anything you can do in Ruby including monkey patch Java classes.
I’ve only shown a couple of examples, but I’ve run into several others (nokogiri,
send_file when using JRuby and more). The point is you will likely have to hack and monkey patch especially as you get into using more 3rd party gems or newer versions of JRuby.
One suggestion I’d like to close with is maintaining consistency in your projects. Whether working by yourself or with a team it’s a good idea to be consistent with where and how you apply hacks and monkey patches. With Rails applications, I like to create a
patches directory in the
config/initializers directory. Documenting the patches (or hacks) in a README, a wiki or some other site accessible to you, your team or other users will help avoid confusion and potential issues in the future.