Class: Spawnling

Inherits:
Object
  • Object
show all
Defined in:
lib/spawnling.rb,
lib/spawnling/version.rb

Constant Summary collapse

RAILS_1_x =
nil
RAILS_2_2 =
nil
RAILS_3_x =
nil
VERSION =
'2.1.6'
@@default_options =
{
  # default to forking (unless windows or jruby)
  :method => ((RUBY_PLATFORM =~ /(win32|mingw32|java)/) ? :thread : :fork),
  :nice   => nil,
  :kill   => false,
  :argv   => nil,
  :detach => true
}
@@resources =

things to close in child process

[]
@@punks =

forked children to kill on exit

[]
@@logger =

in some environments, logger isn’t defined

defined?(::Rails) ? ::Rails.logger : ::Logger.new(STDERR)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}, &block) ⇒ Spawnling

Spawns a long-running section of code and returns the ID of the spawned process. By default the process will be a forked process. To use threading, pass :method => :thread or override the default behavior in the environment by setting ‘Spawnling::method :thread’.



101
102
103
# File 'lib/spawnling.rb', line 101

def initialize(opts = {}, &block)
  @type, @handle = self.class.run(opts, &block)
end

Instance Attribute Details

#handleObject

Returns the value of attribute handle.



37
38
39
# File 'lib/spawnling.rb', line 37

def handle
  @handle
end

#typeObject

Returns the value of attribute type.



36
37
38
# File 'lib/spawnling.rb', line 36

def type
  @type
end

Class Method Details

.alive?(pid) ⇒ Boolean

Returns:

  • (Boolean)


72
73
74
75
76
77
78
79
80
# File 'lib/spawnling.rb', line 72

def self.alive?(pid)
  begin
    Process::kill 0, pid
    # if the process is alive then kill won't throw an exception
    true
  rescue Errno::ESRCH
    false
  end
end

.allow_concurrency?Boolean

Returns:

  • (Boolean)


126
127
128
129
130
131
132
133
134
135
# File 'lib/spawnling.rb', line 126

def self.allow_concurrency?
  return true if RAILS_2_2
  if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:allow_concurrency)
    ActiveRecord::Base.allow_concurrency
  elsif defined?(Rails) && Rails.application
    Rails.application.config.allow_concurrency
  else
    true # assume user knows what they are doing
  end
end

.close_resourcesObject

close all the resources added by calls to resource_to_close



64
65
66
67
68
69
70
# File 'lib/spawnling.rb', line 64

def self.close_resources
  @@resources.each do |resource|
    resource.close if resource && resource.respond_to?(:close) && !resource.closed?
  end
  # in case somebody spawns recursively
  @@resources.clear
end

.default_options(options = {}) ⇒ Object

Set the options to use every time spawn is called unless specified otherwise. For example, in your environment, do something like this:

Spawnling::default_options = {:nice => 5}

to default to using the :nice option with a value of 5 on every call. Valid options are:

:method => (:thread | :fork | :yield)
:nice   => nice value of the forked process
:kill   => whether or not the parent process will kill the
           spawned child process when the parent exits
:argv   => changes name of the spawned process as seen in ps
:detach => whether or not Process.detach is called for spawned child
           processes.  You must wait for children on your own if you
           set this to false


53
54
55
56
# File 'lib/spawnling.rb', line 53

def self.default_options(options = {})
  @@default_options.merge!(options)
  @@logger.info "spawn> default options = #{options.inspect}" if @@logger
end

.kill_punksObject



82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/spawnling.rb', line 82

def self.kill_punks
  @@punks.each do |punk|
    if alive?(punk)
      @@logger.info "spawn> parent(#{Process.pid}) killing child(#{punk})" if @@logger
      begin
        Process.kill("TERM", punk)
      rescue
      end
    end
  end
  @@punks = []
end

.logger=(logger) ⇒ Object



32
33
34
# File 'lib/spawnling.rb', line 32

def self.logger=(logger)
  @@logger = logger
end

.resources_to_close(*resources) ⇒ Object

set the resources to disconnect from in the child process (when forking)



59
60
61
# File 'lib/spawnling.rb', line 59

def self.resources_to_close(*resources)
  @@resources = resources
end

.run(opts = {}, &block) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/spawnling.rb', line 105

def self.run(opts = {}, &block)
  raise "Must give block of code to be spawned" unless block_given?
  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 allow_concurrency?
     return :thread, thread_it(options) { yield }
    else
      @@logger.error("spawn(:method=>:thread) only allowed when allow_concurrency=true")
      raise "spawn requires config.active_record.allow_concurrency=true when used with :method=>:thread"
    end
  else
    return :fork, fork_it(options) { yield }
  end
end

.wait(sids = []) ⇒ Object



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/spawnling.rb', line 137

def self.wait(sids = [])
  # wait for all threads and/or forks (if a single sid passed in, convert to array first)
  Array(sids).each do |sid|
    if sid.type == :thread
      sid.handle.join()
    else
      begin
        Process.wait(sid.handle)
      rescue
        # if the process is already done, ignore the error
      end
    end
  end
  # clean up connections from expired threads
  clean_connections
end