Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ritchiey/1560215 to your computer and use it in GitHub Desktop.
Save ritchiey/1560215 to your computer and use it in GitHub Desktop.
Thread-safe JRuby on Rails HOW-TO with the help of Warbler, Apache Tomcat and JNDI (WIP)

#Thread-safe JRuby on Rails HOW-TO

###With the help of Warbler, Apache Tomcat and JNDI

###Read those posts before anything else otherwise you'll find yourself in a very lonely place :

##Prerequisites A Rails 3.1 app with a database like MySQL, PostgreSQL, Oracle ... forget SQLite.

In this HOW-TO we will use a PostgreSQL database (because I like it ;) ) and an Apache Tomcat Java web server, because it's the most popular.

Add ActiveRecord JDBC PostgreSQL Adapter to your Gemfile, like this :

if defined?(JRUBY_VERSION)
  gem 'activerecord-jdbcpostgresql-adapter'
  gem 'jruby-openssl'
else
  gem 'pg'
end

Add Warbler to your Gemfile, like this :

  if defined?(JRUBY_VERSION)
    group :development do
      gem 'warbler'
    end
  end

Run this command in a terminal: bundle install

Then run this one: bundle exec warble config

##Config Rails in thread safe mode

In your RAILS_ROOT, open the file config\environments\production.rb And uncomment this line : # config.threadsafe!

Then add those lines to ensure you can use rake tasks in production env :

  # Allow rake tasks to autoload models in thread safe mode, more info at http://stackoverflow.com/a/4880253
  config.dependency_loading = true if $rails_rake_task

Finally in your RAILS_ROOT open the file config\warble.rb And replace these 2 lines :

# config.webxml.jruby.min.runtimes = 2
# config.webxml.jruby.max.runtimes = 4

With these lines :

config.webxml.jruby.min.runtimes = 1
config.webxml.jruby.max.runtimes = 1

Create the JNDI connection and configure Rails

In your Apache Tomcat directory, open the file conf\context.xml Add the Resource tag inside the Context tag, like this :

<Context>
<!-- ... -->
  <Resource name="jdbc/your_jndi_name" auth="Container" type="javax.sql.DataSource"
    maxActive="100" maxIdle="30" maxWait="10000"
    username="your_username" password="your_password" driverClassName="org.postgresql.Driver"
    url="jdbc:postgresql://your_hostname:5432/your_database_name"/>

</Context>

Copy your JDBC driver into the Apache Tomcat lib folder

For Ubuntu : sudo cp ~/.rvm/gems/jruby/gems/jdbc-postgres-9.0.801/lib/*.jar /usr/share/tomcat6/lib

Then in your RAILS_ROOT open the file config\database.yml And replace the production config with this one :

production:
  adapter: jdbc
  jndi: java:comp/env/jdbc/your_jndi_name
  driver: postgresql
  encoding: utf8
  wait_timeout: 5
  pool: 5

In your RAILS_ROOT open the file config\warble.rb And replace this line : # config.webxml.jndi = 'jdbc/rails'

With this one : config.webxml.jndi = 'jdbc/your_jndi_name'

Finally create a config/initializers/connection_pool_fix.rb file with this content :

# Monkey patch ConnectionPool#checkout to avoid database connection timeouts
# Source : https://github.com/rails/rails/issues/2547
# For Rails 3.2.0 and upper, You need to check if the pool error still occurs
if Rails.version < "3.2.0"
  class ActiveRecord::ConnectionAdapters::ConnectionPool
    def checkout
      # Checkout an available connection
      @connection_mutex.synchronize do
        loop do
          conn = if @checked_out.size < @connections.size
                   checkout_existing_connection
                 elsif @connections.size < @size
                   checkout_new_connection
                 end
          return conn if conn
  
          # No connections available; wait for one
          if @queue.wait(@timeout)
            next
          else
            # try looting dead threads
            clear_stale_cached_connections!
            if @size == @checked_out.size
              raise ConnectionTimeoutError, "could not obtain a database connection#{" within #{@timeout} seconds" if @timeout}.  The max pool size is currently #{@size}; consider increasing it."
            end
          end
          
        end
      end
    end
  end
end

Create the War file and restart Apache Tomcat

Run this command in a terminal : bundle exec warble

Stop the Apache Tomcat service : sudo service tomcat6 stop

In the Apache Tomcat directory, copy your my_app.war file inside the webapps folder.

Restart the Apache Tomcat service : sudo service tomcat6 start

Enjoy !

@bruceadams
Copy link

The "driver: postgresql" line should be removed from database.yml. It is not needed. The Ruby side of things does not need to load the JDBC driver, Tomcat will take care of that. Having that line there can trigger this error message:

org.jruby.rack.RackInitializationException: jdbc adapter requires driver class and url

@npassaro
Copy link

npassaro commented Jul 1, 2014

What about the pool? Shouldn't it be Tomcat's responsibility to control the number of connections?

@ksader-edmunds
Copy link

Is the monkey patch still needed for Rails 4.0+?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment