Thread-safe JRuby on Rails HOW-TO
With the help of Warbler, Apache Tomcat and JNDI Connection Pool
Read those posts before anything else otherwise you’ll find yourself in a very lonely place:
- JRuby on Rails and Thread Safety
- Q/A: What Thread-safe Rails Means
- Using JDBC Connection Pools with NetBeans 6, JRuby RoR, MySQL and Glassfish
- Apache Tomcat 6.0 - JNDI Datasource HOW-TO
Prerequisites
A JVM must be installed on your system.
Install JRuby (RVM is recommended rvm install jruby
).
If you have installed RVM, run this command in a terminal rvm use jruby
You need Rails 3.1 app with a database like MySQL, PostgreSQL, Oracle … forget SQLite.
If you don’t have one, run these commands in a terminal:
gem install rails
rails new your_rails_app
cd your_rails_app
bundle exec rails generate scaffold Post title:string body:text
In this HOW-TO we will use a PostgreSQL database (because I like it ;) ) and Apache Tomcat 6 or upper, 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
Migrate database, create JNDI connection & configure Rails
Ensure your database production environment is configured, then run these commands in a terminal:
bundle exec rake db:create RAILS_ENV=production
bundle exec rake db:migrate RAILS_ENV=production
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-1.6.5/gems/jdbc-postgres-9.*/lib/*.jar /usr/share/tomcat6/lib
In your RAILS_ROOT open the file config/database.yml
.
Rename the production
entry in production_jdbc
and add 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 these commands in a terminal:
bundle exec rake assets:precompile
bundle exec warble
Stop the Apache Tomcat service: sudo service tomcat6 stop
In the Apache Tomcat directory, copy your your_rails_app.war
file inside the webapps
folder.
For Ubuntu: sudo cp your_rails_app.war /var/lib/tomcat6/webapps
Restart the Apache Tomcat service: sudo service tomcat6 start
Open this URL with your Web Browser: http://localhost:8080/your_rails_app/posts
Enjoy !
Special thanks to Ritchie Young who corrected my spelling mistakes.
View Comments