tastebook-spawn 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. data/CHANGELOG +51 -0
  2. data/LICENSE +22 -0
  3. data/README.markdown +194 -0
  4. data/lib/spawn.rb +191 -0
  5. metadata +60 -0
data/CHANGELOG ADDED
@@ -0,0 +1,51 @@
1
+ v0.1 - 2007/09/13
2
+
3
+ initial version
4
+
5
+ --------------------------------------------------
6
+ v0.2 - 2007/09/28
7
+
8
+ * return PID of the child process
9
+ * added ":detach => false" option
10
+
11
+ --------------------------------------------------
12
+ v0.3 - 2007/10/15
13
+
14
+ * added ':method => :thread' for threaded spawns
15
+ * removed ':detach => false' option in favor of more generic implementation
16
+ * added ability to set configuration of the form 'Spawn::method :thread'
17
+ * added patch to ActiveRecord::Base to allow for more efficient reconnect in child processes
18
+ * added monkey patch for http://dev.rubyonrails.org/ticket/7579
19
+ * added wait() method to wait for spawned code blocks
20
+ * don't allow threading if allow_concurrency=false
21
+
22
+ --------------------------------------------------
23
+ v0.4 - 2008/1/26
24
+
25
+ * default to :thread on windows, still :fork on all other platforms
26
+ * raise exception when used with :method=>:true and allow_concurrency != true
27
+
28
+ --------------------------------------------------
29
+ v0.5 - 2008/3/1
30
+ * also default to :thread on JRuby (java)
31
+ * added new :method => :yield which doesn't fork or thread, this is useful for testing
32
+ * fixed problem with connections piling up on PostgreSQL
33
+
34
+ --------------------------------------------------
35
+ v0.6 - 2008/04/21
36
+ * only apply clear_reloadable_connections patch on Rails 1.x (7579 fixed in Rails 2.x)
37
+ * made it more responsive in more environments by disconnecting from the listener socket in the forked process
38
+
39
+ --------------------------------------------------
40
+ v0.7 - 2008/04/24
41
+ * more generic mechanism for closing resources after fork
42
+ * check for existence of Mongrel before patching it
43
+
44
+ --------------------------------------------------
45
+ v0.8 - 2008/05/02
46
+ * call exit! within the ensure block so that at_exit handlers aren't called on exceptions
47
+ * set logger from RAILS_DEFAULT_LOGGER if available, else STDERR
48
+
49
+ --------------------------------------------------
50
+ v0.9 - 2008/05/11
51
+ * added ability to set nice level for child process
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2007 Tom Anderson (tom@squeat.com)
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,194 @@
1
+ # Spawn
2
+
3
+ This plugin provides a 'spawn' method to easily fork OR thread long-running sections of
4
+ code so that your application can return results to your users more quickly.
5
+ This plugin works by creating new database connections in ActiveRecord::Base for the
6
+ spawned block.
7
+
8
+ The plugin also patches ActiveRecord::Base to handle some known bugs when using
9
+ threads (see lib/patches.rb).
10
+
11
+ ## Installation
12
+
13
+ Use
14
+
15
+ gem "spawn", :git => 'git://github.com/rfc2822/spawn'
16
+
17
+ in your Gemfile and use bundler to manage it (bundle install, bundle update).
18
+
19
+ Make sure that ActiveRecord reconnects to your database automatically when needed,
20
+ for instance put
21
+
22
+ production/development:
23
+ ...
24
+ reconnect: true
25
+
26
+ into your config/database.yml.
27
+
28
+ ## Usage
29
+
30
+ Here's a simple example of how to demonstrate the spawn plugin.
31
+ In one of your controllers, insert this code (after installing the plugin of course):
32
+
33
+ spawn_block do
34
+ logger.info("I feel sleepy...")
35
+ sleep 11
36
+ logger.info("Time to wake up!")
37
+ end
38
+
39
+ If everything is working correctly, your controller should finish quickly then you'll see
40
+ the last log message several seconds later.
41
+
42
+ If you need to wait for the spawned processes/threads, then pass the objects returned by
43
+ spawn to Spawn::wait(), like this:
44
+
45
+ N.times do |i|
46
+ # spawn N blocks of code
47
+ spawn_ids[i] = spawn_block do
48
+ something(i)
49
+ end
50
+ end
51
+ # wait for all N blocks of code to finish running
52
+ wait(spawn_ids)
53
+
54
+ ## Options
55
+
56
+ The options you can pass to spawn_block are:
57
+
58
+ <table>
59
+ <tr><th>Option</th><th>Values</th></tr>
60
+ <tr><td>:method</td><td>:fork, :thread, :yield</td></tr>
61
+ <tr><td>:nice</td><td>integer value 0-19, 19 = really nice</td></tr>
62
+ <tr><td>:kill</td><td>boolean value indicating whether the parent should kill the spawned process
63
+ when it exits (only valid when :method => :fork)</td></tr>
64
+ <tr><td>:argv</td><td>string to override the process name</td></tr>
65
+ </table>
66
+
67
+ Any option to spawn_block can be set as a default so that you don't have to pass them in
68
+ to every call of spawn_block. To configure the spawn default options, add a line to
69
+ your configuration file(s) like this:
70
+
71
+ Spawn::default_options {:method => :thread}
72
+
73
+ If you don't set any default options, the :method will default to :fork. To
74
+ specify different values for different environments, add the default_options call to
75
+ he appropriate environment file (development.rb, test.rb). For testing you can set
76
+ the default :method to :yield so that the code is run inline.
77
+
78
+ # in environment.rb
79
+ Spawn::method :method => :fork, :nice => 7
80
+ # in test.rb, will override the environment.rb setting
81
+ Spawn::method :method => :yield
82
+
83
+ This allows you to set your production and development environments to use different
84
+ methods according to your needs.
85
+
86
+ ### be nice
87
+
88
+ If you want your forked child to run at a lower priority than the parent process, pass in
89
+ the :nice option like this:
90
+
91
+ spawn_block(:nice => 7) do
92
+ do_something_nicely
93
+ end
94
+
95
+ ### fork me
96
+
97
+ By default, spawn will use the fork to spawn child processes. You can configure it to
98
+ do threading either by telling the spawn method when you call it or by configuring your
99
+ environment.
100
+ For example, this is how you can tell spawn to use threading on the call,
101
+
102
+ spawn_block(:method => :thread) do
103
+ something
104
+ end
105
+
106
+ When you use threaded spawning, make sure that your application is thread-safe. Rails
107
+ can be switched to thread-safe mode with
108
+
109
+ # Enable threaded mode
110
+ config.threadsafe!
111
+
112
+ in environments/your_environment.rb
113
+
114
+ ### kill or be killed
115
+
116
+ Depending on your application, you may want the children processes to go away when
117
+ the parent process exits. By default spawn lets the children live after the
118
+ parent dies. But you can tell it to kill the children by setting the :kill option
119
+ to true.
120
+
121
+ ### a process by any other name
122
+
123
+ If you'd like to be able to identify which processes are spawned by looking at the
124
+ output of ps then set the :argv option with a string of your choice.
125
+ You should then be able to see this string as the process name when
126
+ listing the running processes (ps).
127
+
128
+ For example, if you do something like this,
129
+
130
+ 3.times do |i|
131
+ spawn_block(:argv => "spawn -#{i}-") do
132
+ something(i)
133
+ end
134
+ end
135
+
136
+ then in the shell,
137
+
138
+ $ ps -ef | grep spawn
139
+ 502 2645 2642 0 0:00.01 ttys002 0:00.02 spawn -0-
140
+ 502 2646 2642 0 0:00.02 ttys002 0:00.02 spawn -1-
141
+ 502 2647 2642 0 0:00.02 ttys002 0:00.03 spawn -2-
142
+
143
+ The length of the process name may be limited by your OS so you might want to experiment
144
+ to see how long it can be (it may be limited by the length of the original process name).
145
+
146
+ ## Forking vs. Threading
147
+
148
+ There are several tradeoffs for using threading vs. forking. Forking was chosen as the
149
+ default primarily because it requires no configuration to get it working out of the box.
150
+
151
+ Forking advantages:
152
+
153
+ - more reliable? - the ActiveRecord code is generally not deemed to be thread-safe.
154
+ Even though spawn attempts to patch known problems with the threaded implementation,
155
+ there are no guarantees. Forking is heavier but should be fairly reliable.
156
+ - keep running - this could also be a disadvantage, but you may find you want to fork
157
+ off a process that could have a life longer than its parent. For example, maybe you
158
+ want to restart your server without killing the spawned processes.
159
+ We don't necessarily condone this (i.e. haven't tried it) but it's technically possible.
160
+ - easier - forking works out of the box with spawn, threading requires you set
161
+ allow_concurrency=true (for older versions of Rails).
162
+ Also, beware of automatic reloading of classes in development
163
+ mode (config.cache_classes = false).
164
+
165
+ Threading advantages:
166
+ - less filling - threads take less resources... how much less? it depends. Some
167
+ flavors of Unix are pretty efficient at forking so the threading advantage may not
168
+ be as big as you think... but then again, maybe it's more than you think. ;-)
169
+ - debugging - you can set breakpoints in your threads
170
+
171
+ ## Acknowledgements
172
+
173
+ This plugin was initially inspired by Scott Persinger's blog post on how to use fork
174
+ in rails for background processing.
175
+ http://geekblog.vodpod.com/?p=26
176
+
177
+ Further inspiration for the threading implementation came from Jonathon Rochkind's
178
+ blog post on threading in rails.
179
+ http://bibwild.wordpress.com/2007/08/28/threading-in-rails/
180
+
181
+ Also thanks to all who have helped debug problems and suggest improvements
182
+ including:
183
+
184
+ - Ahmed Adam, Tristan Schneiter, Scott Haug, Andrew Garfield, Eugene Otto, Dan Sharp,
185
+ Olivier Ruffin, Adrian Duyzer, Cyrille Labesse
186
+
187
+ - Garry Tan, Matt Jankowski (Rails 2.2.x fixes), Mina Naguib (Rails 2.3.6 fix)
188
+
189
+ - Tim Kadom, Mauricio Marcon Zaffari, Danial Pearce, Hongli Lai, Scott Wadden
190
+ (passenger fixes)
191
+
192
+ - &lt;your name here&gt;
193
+
194
+ Copyright (c) 2007-present Tom Anderson (tom@squeat.com), see LICENSE
data/lib/spawn.rb ADDED
@@ -0,0 +1,191 @@
1
+ module Spawn
2
+ @@default_options = {
3
+ # default to forking (unless windows or jruby)
4
+ :method => ((RUBY_PLATFORM =~ /(win32|java|mingw32)/) ? :thread : :fork),
5
+ :nice => nil,
6
+ :kill => false,
7
+ :argv => nil
8
+ }
9
+
10
+ # things to close in child process
11
+ @@resources = []
12
+ # in some environments, logger isn't defined
13
+ @@logger = Rails.logger || Logger.new(STDERR)
14
+ # forked children to kill on exit
15
+ @@punks = []
16
+
17
+ # Set the options to use every time spawn is called unless specified
18
+ # otherwise. For example, in your environment, do something like
19
+ # this:
20
+ # Spawn::default_options = {:nice => 5}
21
+ # to default to using the :nice option with a value of 5 on every call.
22
+ # Valid options are:
23
+ # :method => (:thread | :fork | :yield)
24
+ # :nice => nice value of the forked process
25
+ # :kill => whether or not the parent process will kill the
26
+ # spawned child process when the parent exits
27
+ # :argv => changes name of the spawned process as seen in ps
28
+ def self.default_options(options = {})
29
+ @@default_options.merge!(options)
30
+ @@logger.info "spawn> default options = #{options.inspect}"
31
+ end
32
+
33
+ # set the resources to disconnect from in the child process (when forking)
34
+ def self.resources_to_close(*resources)
35
+ @@resources = resources
36
+ end
37
+
38
+ # close all the resources added by calls to resource_to_close
39
+ def self.close_resources
40
+ @@resources.each do |resource|
41
+ resource.close if resource && resource.respond_to?(:close) && !resource.closed?
42
+ end
43
+ # in case somebody spawns recursively
44
+ @@resources.clear
45
+ end
46
+
47
+ def self.alive?(pid)
48
+ begin
49
+ Process::kill 0, pid
50
+ # if the process is alive then kill won't throw an exception
51
+ true
52
+ rescue Errno::ESRCH
53
+ false
54
+ end
55
+ end
56
+
57
+ def self.kill_punks
58
+ @@punks.each do |punk|
59
+ if alive?(punk)
60
+ @@logger.info "spawn> parent(#{Process.pid}) killing child(#{punk})"
61
+ begin
62
+ Process.kill("TERM", punk)
63
+ rescue
64
+ end
65
+ end
66
+ end
67
+ @@punks = []
68
+ end
69
+ # register to kill marked children when parent exits
70
+ at_exit {kill_punks}
71
+
72
+ # Spawns a long-running section of code and returns the ID of the spawned process.
73
+ # By default the process will be a forked process. To use threading, pass
74
+ # :method => :thread or override the default behavior in the environment by setting
75
+ # 'Spawn::method :thread'.
76
+ def spawn_block(opts = {})
77
+ options = @@default_options.merge(opts.symbolize_keys)
78
+ # setting options[:method] will override configured value in default_options[:method]
79
+ if options[:method] == :yield
80
+ yield
81
+ elsif options[:method] == :thread || (options[:method] == nil && @@method == :thread)
82
+ thread_it(options) { yield }
83
+ else
84
+ fork_it(options) { yield }
85
+ end
86
+ end
87
+
88
+ def wait(sids = [])
89
+ # wait for all threads and/or forks (if a single sid passed in, convert to array first)
90
+ Array(sids).each do |sid|
91
+ if sid.type == :thread
92
+ sid.handle.join()
93
+ else
94
+ begin
95
+ Process.wait(sid.handle)
96
+ rescue
97
+ # if the process is already done, ignore the error
98
+ end
99
+ end
100
+ end
101
+ # clean up connections from expired threads
102
+ ActiveRecord::Base.verify_active_connections!() if defined? ActiveRecord
103
+ end
104
+
105
+ class SpawnId
106
+ attr_accessor :type
107
+ attr_accessor :handle
108
+ def initialize(t, h)
109
+ self.type = t
110
+ self.handle = h
111
+ end
112
+ end
113
+
114
+ protected
115
+ def fork_it(options)
116
+ # The problem with rails is that it only has one connection (per class),
117
+ # so when we fork a new process, we need to reconnect.
118
+ @@logger.debug "spawn> parent PID = #{Process.pid}"
119
+ child = fork do
120
+ begin
121
+ start = Time.now
122
+ @@logger.debug "spawn> child PID = #{Process.pid}"
123
+
124
+ # this child has no children of it's own to kill (yet)
125
+ @@punks = []
126
+
127
+ # set the nice priority if needed
128
+ Process.setpriority(Process::PRIO_PROCESS, 0, options[:nice]) if options[:nice]
129
+
130
+ # disconnect from the listening socket, et al
131
+ Spawn.close_resources
132
+ # get a new connection so the parent can keep the original one
133
+ # Old spawn did a bunch of hacks inside activerecord here. There is
134
+ # most likely a reason that this won't work, but I am dumb.
135
+ ActiveRecord::Base.connection.reconnect! if defined? ActiveRecord
136
+
137
+ # set the process name
138
+ $0 = options[:argv] if options[:argv]
139
+
140
+ # run the block of code that takes so long
141
+ yield
142
+
143
+ rescue => ex
144
+ @@logger.error "spawn> Exception in child[#{Process.pid}] - #{ex.class}: #{ex.message}"
145
+ ensure
146
+ begin
147
+ # to be safe, catch errors on closing the connnections too
148
+ ActiveRecord::Base.connection_handler.clear_all_connections! if defined? ActiveRecord
149
+ ensure
150
+ @@logger.info "spawn> child[#{Process.pid}] took #{Time.now - start} sec"
151
+ # ensure log is flushed since we are using exit!
152
+ @@logger.flush if @@logger.respond_to?(:flush)
153
+ # this child might also have children to kill if it called spawn
154
+ Spawn::kill_punks
155
+ # this form of exit doesn't call at_exit handlers
156
+ exit!(0)
157
+ end
158
+ end
159
+ end
160
+
161
+ # detach from child process (parent may still wait for detached process if they wish)
162
+ Process.detach(child)
163
+
164
+ # remove dead children from the target list to avoid memory leaks
165
+ @@punks.delete_if {|punk| !Spawn::alive?(punk)}
166
+
167
+ # mark this child for death when this process dies
168
+ if options[:kill]
169
+ @@punks << child
170
+ @@logger.debug "spawn> death row = #{@@punks.inspect}"
171
+ end
172
+
173
+ return SpawnId.new(:fork, child)
174
+ end
175
+
176
+ def thread_it(options)
177
+ # clean up stale connections from previous threads
178
+ ActiveRecord::Base.verify_active_connections!() if defined? ActiveRecord
179
+ thr = Thread.new do
180
+ # run the long-running code block
181
+ yield
182
+ end
183
+ thr.priority = -options[:nice] if options[:nice]
184
+ return SpawnId.new(:thread, thr)
185
+ end
186
+ end
187
+
188
+
189
+ ActiveRecord::Base.send :include, Spawn if defined? ActiveRecord
190
+ ActionController::Base.send :include, Spawn
191
+ ActiveRecord::Observer.send :include, Spawn if defined? ActiveRecord
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tastebook-spawn
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tom Anderson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2010-08-08 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ! 'This plugin provides a ''spawn'' method to easily fork OR
15
+
16
+ thread long-running sections of code so that your application can return
17
+
18
+ results to your users more quickly. This plugin works by creating new database
19
+
20
+ connections in ActiveRecord::Base for the spawned block.
21
+
22
+
23
+ The plugin also patches ActiveRecord::Base to handle some known bugs when using
24
+
25
+ threads (see lib/patches.rb).'
26
+ email:
27
+ - tom@squeat.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - lib/spawn.rb
33
+ - CHANGELOG
34
+ - LICENSE
35
+ - README.markdown
36
+ homepage: http://github.com/tra/spawn
37
+ licenses: []
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: 1.3.6
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 1.8.17
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: Easily fork OR thread long-running sections of code in Ruby
60
+ test_files: []