Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #30 from diminish7/no_rails
Allow spawn to work in the absence of ActiveRecord
  • Loading branch information
tra committed Feb 6, 2013
2 parents 0e1ab99 + 9cbdacd commit 1c43d23
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 52 deletions.
80 changes: 41 additions & 39 deletions lib/patches.rb
@@ -1,50 +1,52 @@
# see activerecord/lib/active_record/connection_adaptors/abstract/connection_specification.rb
class ActiveRecord::Base
# reconnect without disconnecting
if Spawn::RAILS_3_x || Spawn::RAILS_2_2
def self.spawn_reconnect(klass=self)
# keep ancestors' connection_handlers around to avoid them being garbage collected in the forked child
@@ancestor_connection_handlers ||= []
@@ancestor_connection_handlers << self.connection_handler
self.connection_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
if defined?(ActiveRecord)
# see activerecord/lib/active_record/connection_adaptors/abstract/connection_specification.rb
class ActiveRecord::Base
# reconnect without disconnecting
if Spawn::RAILS_3_x || Spawn::RAILS_2_2
def self.spawn_reconnect(klass=self)
# keep ancestors' connection_handlers around to avoid them being garbage collected in the forked child
@@ancestor_connection_handlers ||= []
@@ancestor_connection_handlers << self.connection_handler
self.connection_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new

establish_connection
end
else
def self.spawn_reconnect(klass=self)
spec = @@defined_connections[klass.name]
konn = active_connections[klass.name]
# remove from internal arrays before calling establish_connection so that
# the connection isn't disconnected when it calls AR::Base.remove_connection
@@defined_connections.delete_if { |key, value| value == spec }
active_connections.delete_if { |key, value| value == konn }
establish_connection(spec ? spec.config : nil)
establish_connection
end
else
def self.spawn_reconnect(klass=self)
spec = @@defined_connections[klass.name]
konn = active_connections[klass.name]
# remove from internal arrays before calling establish_connection so that
# the connection isn't disconnected when it calls AR::Base.remove_connection
@@defined_connections.delete_if { |key, value| value == spec }
active_connections.delete_if { |key, value| value == konn }
establish_connection(spec ? spec.config : nil)
end
end
end

# this patch not needed on Rails 2.x and later
if Spawn::RAILS_1_x
# monkey patch to fix threading problems,
# see: http://dev.rubyonrails.org/ticket/7579
def self.clear_reloadable_connections!
if @@allow_concurrency
# Hash keyed by thread_id in @@active_connections. Hash of hashes.
@@active_connections.each do |thread_id, conns|
conns.each do |name, conn|
# this patch not needed on Rails 2.x and later
if Spawn::RAILS_1_x
# monkey patch to fix threading problems,
# see: http://dev.rubyonrails.org/ticket/7579
def self.clear_reloadable_connections!
if @@allow_concurrency
# Hash keyed by thread_id in @@active_connections. Hash of hashes.
@@active_connections.each do |thread_id, conns|
conns.each do |name, conn|
if conn.requires_reloading?
conn.disconnect!
@@active_connections[thread_id].delete(name)
end
end
end
else
# Just one level hash, no concurrency.
@@active_connections.each do |name, conn|
if conn.requires_reloading?
conn.disconnect!
@@active_connections[thread_id].delete(name)
@@active_connections.delete(name)
end
end
end
else
# Just one level hash, no concurrency.
@@active_connections.each do |name, conn|
if conn.requires_reloading?
conn.disconnect!
@@active_connections.delete(name)
end
end
end
end
end
Expand Down
43 changes: 30 additions & 13 deletions lib/spawn.rb
@@ -1,8 +1,15 @@
module Spawn
require 'logger'

RAILS_1_x = (::Rails::VERSION::MAJOR == 1) unless defined?(RAILS_1_x)
RAILS_2_2 = ((::Rails::VERSION::MAJOR == 2 && ::Rails::VERSION::MINOR >= 2)) unless defined?(RAILS_2_2)
RAILS_3_x = (::Rails::VERSION::MAJOR > 2) unless defined?(RAILS_3_x)
module Spawn
if defined? Rails
RAILS_1_x = (::Rails::VERSION::MAJOR == 1) unless defined?(RAILS_1_x)
RAILS_2_2 = ((::Rails::VERSION::MAJOR == 2 && ::Rails::VERSION::MINOR >= 2)) unless defined?(RAILS_2_2)
RAILS_3_x = (::Rails::VERSION::MAJOR > 2) unless defined?(RAILS_3_x)
else
RAILS_1_x = nil
RAILS_2_2 = nil
RAILS_3_x = nil
end

@@default_options = {
# default to forking (unless windows or jruby)
Expand Down Expand Up @@ -111,15 +118,15 @@ def self.kill_punks
# :method => :thread or override the default behavior in the environment by setting
# 'Spawn::method :thread'.
def spawn(opts = {})
options = @@default_options.merge(opts.symbolize_keys)
options = @@default_options.merge(symbolize_options(opts))
# setting options[:method] will override configured value in default_options[:method]
if options[:method] == :yield
yield
elsif options[:method].respond_to?(:call)
options[:method].call(proc { yield })
elsif options[:method] == :thread
# for versions before 2.2, check for allow_concurrency
if RAILS_2_2 || ActiveRecord::Base.respond_to?(:allow_concurrency) ?
if RAILS_2_2 || ActiveRecord::Base.respond_to?(:allow_concurrency) ?
ActiveRecord::Base.allow_concurrency : Rails.application.config.allow_concurrency
thread_it(options) { yield }
else
Expand All @@ -145,7 +152,7 @@ def wait(sids = [])
end
end
# clean up connections from expired threads
ActiveRecord::Base.verify_active_connections!()
ActiveRecord::Base.verify_active_connections!() if defined?(ActiveRecord)
end

class SpawnId
Expand Down Expand Up @@ -175,10 +182,12 @@ def fork_it(options)

# disconnect from the listening socket, et al
Spawn.close_resources
# get a new database connection so the parent can keep the original one
ActiveRecord::Base.spawn_reconnect
# close the memcache connection so the parent can keep the original one
Rails.cache.reset if Rails.cache.respond_to?(:reset)
if defined?(Rails)
# get a new database connection so the parent can keep the original one
ActiveRecord::Base.spawn_reconnect
# close the memcache connection so the parent can keep the original one
Rails.cache.reset if Rails.cache.respond_to?(:reset)
end

# set the process name
$0 = options[:argv] if options[:argv]
Expand All @@ -192,7 +201,7 @@ def fork_it(options)
ensure
begin
# to be safe, catch errors on closing the connnections too
ActiveRecord::Base.connection_handler.clear_all_connections!
ActiveRecord::Base.connection_handler.clear_all_connections! if defined?(ActiveRecord)
ensure
@@logger.info "spawn> child[#{Process.pid}] took #{Time.now - start} sec" if @@logger
# ensure log is flushed since we are using exit!
Expand Down Expand Up @@ -222,12 +231,20 @@ def fork_it(options)

def thread_it(options)
# clean up stale connections from previous threads
ActiveRecord::Base.verify_active_connections!()
ActiveRecord::Base.verify_active_connections!() if defined?(ActiveRecord)
thr = Thread.new do
# run the long-running code block
yield
end
thr.priority = -options[:nice] if options[:nice]
return SpawnId.new(:thread, thr)
end

# In case we don't have rails, can't call opts.symbolize_keys
def symbolize_options(hash)
hash.inject({}) do |new_hash, (key, value)|
new_hash[key.to_sym] = value
new_hash
end
end
end

0 comments on commit 1c43d23

Please sign in to comment.