Skip to content

Instantly share code, notes, and snippets.

@jpr5
Created August 9, 2009 02:47
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save jpr5/164587 to your computer and use it in GitHub Desktop.
Sinatra / DM Scavenger Thread issue
The problem is due to ::Extlib::Pooling.scavenger lazy-creating a thread to reap the pool. Call it within the main context (i.e. DO initialization during ::DataMapper.setup) and the hang is avoided.
For a simple solution, see: http://github.com/jpr5/dm-core/commit/3379acf9ad12a154eabb9fcd3148a09f6fbc1a55
== Sinatra/0.10.1 has taken the stage on 4000 for development with backup from Mongrel
Sun, 09 Aug 2009 03:47:26 GMT ~ debug ~ (0.000587) SELECT "id", "session_id", "data", "updated_at" FROM "sessions" WHERE ("session_id" = 'true') ORDER BY "id" LIMIT 1
Sun, 09 Aug 2009 03:47:26 GMT ~ debug ~ (0.000054) SELECT "id", "session_id", "data", "updated_at" FROM "sessions" WHERE ("session_id" = 'true') ORDER BY "id" LIMIT 1
127.0.0.1 - - [08/Aug/2009 20:47:26] "GET /flows HTTP/1.1" 200 155 0.0080
127.0.0.1 - - [08/Aug/2009 20:47:27] "GET /favicon.ico HTTP/1.1" 200 32 0.0014
^CSat Aug 08 20:47:27 -0700 2009: Reaping 1 threads for slow workers because of 'shutdown'
Waiting for 1 requests to finish, could take 60.0 seconds.
== Sinatra has ended his set (crowd applauds)
^C
== Sinatra has ended his set (crowd applauds)
/Library/Ruby/Gems/1.8/gems/mongrel-1.1.5/lib/mongrel.rb:341:in `graceful_shutdown': Mongrel::StopServer (Mongrel::StopServer)
from /Library/Ruby/Gems/1.8/gems/rack-1.0.0/lib/rack/handler/mongrel.rb:34:in `join'
from /Library/Ruby/Gems/1.8/gems/rack-1.0.0/lib/rack/handler/mongrel.rb:34:in `run'
from ./lib/../vendor/sinatra-0.10.1/lib/sinatra/base.rb:888:in `run!'
from app.rb:124
$
require 'rubygems'
require 'rack'
require 'sinatra/base'
require 'dm-core'
DataMapper::Logger.new(STDOUT, :debug)
DataMapper.setup(:default, "sqlite3:foo.db")
# This will create the pooling scavenger thread used by DO inside the main thread,
# avoiding the hang. See explanation below.
::Extlib::Pooling.scavenger
require 'session'
class App < Sinatra::Base
use ::Rack::Session::DataMapper, :key => '_ccsession', :cache => true
get "/*" do
"hi mom: #{params[:splat]}"
end
end
App.run!
# Updated to add generate_sid method into Store.
require 'rack/session/abstract/id'
require 'dm-core'
module Rack
module Session
class DataMapper < ::Rack::Session::Abstract::ID
class Store
def initialize(app, options)
@mutex = Mutex.new
@sidbits = options[:sidbits] || ::Rack::Session::Abstract::ID::DEFAULT_OPTIONS[:sidbits]
if options.delete(:cache)
@@cache = {}
elsif not self.class.class_variable_defined? :@@cache
@@cache = nil
end
unless self.class.class_variable_defined? :@@session_class and @@session_class
@@session_class = options.delete(:session_class) || Session
end
end
def generate_sid
"%0#{@sidbits / 4}x" % rand(2**@sidbits - 1)
end
def get_session(env, sid)
@mutex.lock if env['rack.multithread']
sid ||= generate_sid
session = @@cache && @@cache[sid] || @@session_class.first(:session_id => sid)
[sid, session.nil? ? {} : session.data]
ensure
@mutex.unlock if env['rack.multithread']
end
def set_session(env, sid, session_data, options)
@mutex.lock if env['rack.multithread']
if options[:renew]
@@session_class.all(:session_id => sid).destroy!
@@cache.delete(sid) if @@cache
sid = generate_sid
end
session = @@cache && @@cache[sid] || @@session_class.first_or_create(:session_id => sid)
session.data = session_data
session.updated_at = Time.now if session.dirty?
@@cache[sid] = session if @@cache
if $VERBOSE and session.new?
env['rack.errors'].puts("created new session #{sid}")
end
# docs say this should return true/false, but
# commit_session in abstract relies on the id
# being returned.
session.save ? sid : false
ensure
@mutex.unlock if env['rack.multithread']
end
end
# Use Text instead of Object for data because loading an
# Object property automatically sets the model to dirty
# (DM 0.9.11).
class Session
include ::DataMapper::Resource
def self.name
"session"
end
property :id, Serial
property :session_id, String, :unique_index => true
property :data, Text, :nullable => false, :lazy => false, :auto_validation => false,
:default => ::Base64.encode64(Marshal.dump({}))
property :updated_at, DateTime, :nullable => true, :index => true
def data=(data)
attribute_set(:data, ::Base64.encode64(Marshal.dump(data)))
end
def data
Marshal.load(::Base64.decode64(attribute_get(:data)))
end
end
# use Rack::Session::DataMapper, :opt1 => :foo, :opt2 => :bar
def initialize(app, options)
super
@store = Store.new(app, options)
end
def get_session(env, sid)
@store.get_session(env, sid)
end
def set_session(env, sid, session_data, options)
@store.set_session(env, sid, session_data, options)
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment