Have you considered the possibility that memcache might be recycling keys on you? We had an issue a while back where we were using memcache for both fragment caching and session storage. Occasionally, we would get exceptions in retrieving a session, and looking at the error message it was clear that what was retrieved from memcache was a fragment and not a session. Unfortunately, we moved back to a cookie-based session store before we had a chance to look deeper into the issue. What I can say is that based on the key generation scheme we were using for sessions and cache keys, there was effectively 0 chance that we were duplicating keys. Instead, it seemed like memcache was re-using slots for different keys when we started exhausting free slots. Hope that helps! - Joshua Ballanco On Monday, July 25, 2011 at 2:22 PM, Neil wrote: > While it's entirely possible that this issue is caused by some other > factor, but we are getting session collisions as well as an issue > where one user is getting another user's session. This is clearly > bad, but I cannot for the life of me figure out how this could even > happen in the first place. The code looks thread safe to me, and a > quick discussion on #ruby-lang seems to support that. > > Thoughts: > 1. Session IDs are being generated in the same sequence (uses > securerandom -> openssl which does not have a static seed) > 2. Threads. Looks good to me. > 3. Maybe memcached is returning something other than "STORED/ > NOT_STORED" for @pool.add(sid, session), but the operation still > succeeded? > 4. Gnomes. > > Any input is GREATLY appreciated. Please don't say "it's an RC, what > do you expect?" :) > > > From https://github.com/rack/rack/blob/master/lib/rack/session/memcache.rb > def generate_sid > loop do > sid = super > break sid unless @pool.get(sid, true) > end > end > > def get_session(env, sid) > with_lock(env, [nil, {}]) do > unless sid and session = @pool.get(sid) > sid, session = generate_sid, {} > unless /^STORED/ =~ @pool.add(sid, session) > raise "Session collision on '#{sid.inspect}'" > end > end > [sid, session] > end > end > > def set_session(env, session_id, new_session, options) > expiry = options[:expire_after] > expiry = expiry.nil? ? 0 : expiry + 1 > > with_lock(env, false) do > @pool.set session_id, new_session, expiry > session_id > end > end