jruby/jruby-rack — Issue #95: Java servlet store is not thread-safe
Author: marcusgpeterson
Created: 2012-02-08 16:38:02+00:00
State: open

We had observed that our production (JRuby on Rails 2, Java servlet store) webapp was dropping user session variables in certain situations. After I began to suspect that a race condition was to blame for this behavior, I devised a way to replicate the problem in the example jruby-rack Rails 2 app. Here's how to do it:

1) Add a session attribute (session['bounty_hunters']) to the SnoopController session_form action:

```ruby
def session_form
session[:id]
session['bounty_hunters'] = ['boba_fett', 'ig-88', 'dengar', 'dog']
@session_hash = session.to_hash
end
```
2) Add a new action to SnoopController that logs the contents of the session and complains if session['bounty_hunters'] is nil. This action should sleep for 0-3 seconds to simulate the latency of database access, etc. Note that session access is *read-only* here:

```ruby
def inspect_session
Rails.logger.info "bounty_hunters is nil!" if session['bounty_hunters'] == nil
Rails.logger.info "session : #{session.inspect}"
sleep(rand 3)
render :text => 'ok'
end
```
3) In views/snoop/session_form.html, bind a hyperlink (call it 'inspect') to a Prototype AJAX request that calls the SnoopController's inspect_session action. Add the following to the top of session_form.html:

```html
<%= javascript_include_tag "prototype" %>

inspect
```
4) Start the app with Trinidad in production mode.

5) Record a Selenium (or similar) script that hits the 'inspect' hyperlink many hundreds of times.

6) Play the script and observe the log. Notice that the 'bounty_hunters' session variable is sporadically reported as nil. This symptom implies that it's possible for multiple sessions on the server to be associated with the same JSESSIONID. If the script runs long enough, session['bounty_hunters'] is eventually reported as nil for *every* request, indicating that its original value has been permanently lost during the read-only session access.

Swapping in a different session store (I tried cookie store, active record store, and memcached) eliminates the behavior in all cases - strong evidence that Java servlet store is indeed to blame. Furthermore, single-threading the app *and* using Java servlet store also fixes the problem, which convinces me that servlet store isn't thread-safe.

Here is my gemset:

```
actionmailer (2.3.14)
actionpack (2.3.14)
activerecord (2.3.14)
activerecord-jdbc-adapter (1.2.0)
activeresource (2.3.14)
activesupport (2.3.14)
bouncy-castle-java (1.5.0146.1)
capistrano (2.5.14)
ffi (1.0.11 java)
ffi-locale (1.0.1)
ffi-ncurses (0.4.0)
hflr (1.0.1)
highline (1.6.11)
hpricot (0.8.4 java)
jdbc-mysql (5.1.13)
jruby-jars (1.6.4)
jruby-openssl (0.7.4)
jruby-rack (1.1.3)
json_pure (1.6.1)
mislav-will_paginate (2.3.11)
net-scp (1.0.4)
net-sftp (2.0.5)
net-ssh (2.3.0)
net-ssh-gateway (1.1.0)
rack (1.1.3)
rails (2.3.14)
rake (0.9.2.2)
renum (1.3.1)
rubyzip (0.9.5)
shoulda (2.11.3)
stomp (1.1.6)
trinidad (1.2.3)
trinidad_jars (1.0.2)
warbler (1.3.2)
```