tastebook-spawn 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []