creeper 0.0.5 → 1.0.0

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.
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in creeper.gemspec
4
4
  gemspec
5
+
6
+ gem 'celluloid', git: 'git://github.com/celluloid/celluloid.git'
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 lyon
1
+ Copyright (c) 2012 Andrew Bennett
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,102 +1,29 @@
1
1
  # Creeper
2
2
 
3
- Can be used as an in place drop in for stalker but it is multi threaded so you can easily do more without using more memory
3
+ TODO: Write a gem description
4
4
 
5
- *Note*: Creeper requires Ruby 1.9
5
+ ## Installation
6
6
 
7
- Creeper - an improvement on Stalker
8
- ===================================
7
+ Add this line to your application's Gemfile:
9
8
 
10
- The big difference is how you "work" jobs
9
+ gem 'creeper'
11
10
 
12
- all you need is a runner (thread) count argument :)
11
+ And then execute:
13
12
 
14
- ```
15
- Creeper.work([<job>, ...], <runner_count>)
16
- ```
13
+ $ bundle
17
14
 
18
- [Beanstalkd](http://kr.github.com/beanstalkd/) is a fast, lightweight queueing backend inspired by mmemcached.
15
+ Or install it yourself as:
19
16
 
20
- Queueing jobs
21
- -------------
17
+ $ gem install creeper
22
18
 
23
- From anywhere in your app:
19
+ ## Usage
24
20
 
25
- ```ruby
26
- require 'creeper'
21
+ TODO: Write usage instructions here
27
22
 
28
- Creeper.enqueue('email.send', :to => 'joe@example.com')
29
- Creeper.enqueue('post.cleanup.all')
30
- Creeper.enqueue('post.cleanup', :id => post.id)
31
- ```
23
+ ## Contributing
32
24
 
33
- Working jobs
34
- ------------
35
-
36
- In a standalone file, typically jobs.rb or worker.rb:
37
-
38
- ```ruby
39
- require 'creeper'
40
- include Creeper::Creep
41
-
42
- job 'email.send' do |args|
43
- Pony.send(:to => args['to'], :subject => "Hello there")
44
- end
45
-
46
- job 'post.cleanup.all' do |args|
47
- Post.all.each do |post|
48
- enqueue('post.cleanup', :id => post.id)
49
- end
50
- end
51
-
52
- job 'post.cleanup' do |args|
53
- Post.find(args['id']).cleanup
54
- end
55
-
56
- # All of these Creeper.work calls are equivalent:
57
-
58
- Creeper.work(:all, 3) # work all jobs, 3 threads
59
- Creeper.work(nil, 3) # same as previous line
60
- Creeper.work([ 'email.send', 'post.cleanup.all', 'post.cleanup' ], 3) # same as previous line
61
-
62
- # Here we work just one job:
63
- Creeper.work('email.send', 5) # work 'email.send', 5 threads
64
- ```
65
-
66
- Running
67
- -------
68
-
69
- First, make sure you have Beanstalkd installed and running:
70
-
71
- ```bash
72
- $ sudo brew install beanstalkd
73
- $ beanstalkd
74
- ```
75
-
76
- Creeper:
77
-
78
- ```bash
79
- $ sudo gem install creeper
80
- ```
81
-
82
- Error Handling
83
- -------------
84
-
85
- If you include an `error` block in your jobs definition, that block will be invoked when a worker encounters an error. You might use this to report errors to an external monitoring service:
86
-
87
- ```ruby
88
- error do |e, job, args|
89
- Exceptional.handle(e)
90
- end
91
- ```
92
-
93
- Before filter
94
- -------------
95
-
96
- If you wish to run a block of code prior to any job:
97
-
98
- ```ruby
99
- before do |job|
100
- puts "About to work #{job}"
101
- end
102
- ```
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile CHANGED
@@ -1,6 +1,2 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
- Bundler::GemHelper.install_tasks
4
-
5
- require 'rspec/core/rake_task'
6
- RSpec::Core::RakeTask.new('spec')
@@ -2,11 +2,11 @@
2
2
  require File.expand_path('../lib/creeper/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
- gem.authors = ["lyon"]
6
- gem.email = ["lyondhill@gmail.com"]
7
- gem.description = %q{Stalker with threads}
8
- gem.summary = %q{A better solution for io bound jobs, same as stalker in functionality but more threadie.}
9
- gem.homepage = "https://github.com/lyondhill/creeper"
5
+ gem.authors = ["Lyon Hill", "Andrew Bennett"]
6
+ gem.email = ["lyondhill@gmail.com", "potatosaladx@gmail.com"]
7
+ gem.description = %q{Creeper is an evented version of Stalker}
8
+ gem.summary = %q{A better solution for io bound jobs, same as stalker in functionality but more evented}
9
+ gem.homepage = "https://github.com/potatosalad/creeper"
10
10
 
11
11
  gem.files = `git ls-files`.split($\)
12
12
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -18,10 +18,9 @@ Gem::Specification.new do |gem|
18
18
  gem.add_development_dependency 'pry'
19
19
  gem.add_development_dependency 'rake'
20
20
  gem.add_development_dependency 'rspec'
21
- # gem.add_development_dependency 'stalker'
22
21
 
23
22
  gem.add_dependency 'beanstalk-client'
23
+ gem.add_dependency 'celluloid'
24
+ gem.add_dependency 'em-jack'
24
25
  gem.add_dependency 'kgio'
25
-
26
26
  end
27
-
@@ -1,297 +1,405 @@
1
1
  require 'beanstalk-client'
2
-
3
2
  require 'json'
4
- require 'uri'
5
- require 'timeout'
6
-
7
- require 'fcntl'
8
- require 'etc'
9
- require 'stringio'
10
- require 'kgio'
11
-
12
3
  require 'logger'
4
+ require 'thread'
5
+ require 'timeout'
6
+ require 'uri'
13
7
 
14
8
  require 'creeper/version'
15
- require 'creeper/creep'
16
- require 'creeper/session'
17
- require 'creeper/worker'
18
9
 
19
10
  module Creeper
20
11
 
21
- # These hashes map Threads to Workers
22
- RUNNERS = {}
23
- GRAVEYARD = {}
12
+ class BadURL < RuntimeError; end
13
+
14
+ HANDLERS = {
15
+ named: {},
16
+ before_each: [],
17
+ before_named: {},
18
+ after_each: [],
19
+ after_named: {},
20
+ error_each: [],
21
+ error_named: {},
22
+ finalizers: []
23
+ }
24
24
 
25
- SELF_PIPE = []
25
+ WORKERS = {}
26
26
 
27
- # signal queue used for self-piping
28
- SIG_QUEUE = []
27
+ ## default configuration ##
29
28
 
30
- # list of signals we care about and trap in Creeper
31
- QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :TTIN, :TTOU ]
29
+ @beanstalk_url = ENV['BEANSTALK_URL'] || 'beanstalk://127.0.0.1/'
30
+ @logger = ::Logger.new($stderr)
31
+ @patience_soft = 60
32
+ @patience_hard = 30
33
+ @pool_size = 2
34
+ @retry_count = 3
35
+ @reserve_timeout = 1
32
36
 
33
- START_CTX = {
34
- :argv => ARGV.map { |arg| arg.dup },
35
- 0 => $0.dup,
36
- }
37
- START_CTX[:cwd] = begin
38
- a = File.stat(pwd = ENV['PWD'])
39
- b = File.stat(Dir.pwd)
40
- a.ino == b.ino && a.dev == b.dev ? pwd : Dir.pwd
41
- rescue
42
- Dir.pwd
43
- end
37
+ @lock = Mutex.new
44
38
 
45
- attr_accessor :logger, :error_logger
46
- attr_accessor :job_file, :jobs, :patience, :ready_pipe, :runner_count, :soft_quit, :timeout
39
+ ##
47
40
 
48
- extend self
49
- extend Creeper::Creep
41
+ class << self
50
42
 
51
- ## utilities ##
43
+ ## configuration ##
52
44
 
53
- def logger
54
- @logger ||= Logger.new($stdout)
55
- end
45
+ attr_reader :lock
46
+ attr_accessor :beanstalk_url, :logger, :patience_soft, :patience_hard, :pool_size, :reserve_timeout, :retry_count
56
47
 
57
- def log_exception(prefix, exc, logger = error_logger)
58
- message = exc.message
59
- message = message.dump if /[[:cntrl:]]/ =~ message
60
- logger.error "#{prefix}: #{message} (#{exc.class})"
61
- exc.backtrace.each { |line| logger.error(line) }
62
- end
48
+ def worker_pool
49
+ lock.synchronize do
50
+ @worker_pool
51
+ end
52
+ end
63
53
 
64
- def error_logger
65
- @error_logger ||= Logger.new($stderr)
66
- end
54
+ def worker_pool=(worker_pool)
55
+ lock.synchronize do
56
+ @worker_pool = worker_pool
57
+ end
58
+ end
67
59
 
68
- ##
60
+ def shutdown?
61
+ lock.synchronize do
62
+ !!@shutdown
63
+ end
64
+ end
69
65
 
70
- ## main process ##
66
+ def shutdown=(shutdown)
67
+ lock.synchronize do
68
+ @shutdown = shutdown
69
+ end
70
+ end
71
+
72
+ ##
71
73
 
72
- ### config ###
74
+ ## connection ##
73
75
 
74
- def job_file=(job_file)
75
- (@job_file = job_file).tap do
76
- require File.expand_path(job_file) if job_file
76
+ def beanstalk
77
+ Thread.current[:beanstalk_pool_connection] ||= connect
77
78
  end
78
- end
79
79
 
80
- def patience
81
- @patience ||= 60
82
- end
80
+ def beanstalk_addresses
81
+ uris = beanstalk_url.split(/[\s,]+/)
82
+ uris.map do |uri|
83
+ beanstalk_host_and_port(uri)
84
+ end
85
+ end
83
86
 
84
- def runner_count
85
- @runner_count ||= 1
86
- end
87
+ def connect(addresses = nil)
88
+ Beanstalk::Pool.new(addresses || beanstalk_addresses)
89
+ end
87
90
 
88
- def runner_count=(value)
89
- (@runner_count = value).tap do
90
- reset_proc_name
91
+ def disconnect
92
+ Thread.current[:beanstalk_pool_connection].close rescue nil
93
+ Thread.current[:beanstalk_pool_connection] = nil
91
94
  end
92
- end
93
95
 
94
- def soft_quit
95
- @soft_quit ||= false
96
- end
97
- alias :soft_quit? :soft_quit
96
+ ##
97
+
98
+ ## daemon ##
99
+
100
+ def work(jobs = nil, size = 2)
101
+ require 'creeper/worker'
98
102
 
99
- def soft_quit=(soft_quit)
100
- (@soft_quit = soft_quit).tap do
101
- awaken_creeper if soft_quit?
103
+ Creeper::Worker.work(jobs, size)
102
104
  end
103
- end
104
105
 
105
- def timeout
106
- @timeout ||= 30
107
- end
106
+ ##
108
107
 
109
- ###
108
+ ## handlers ##
110
109
 
111
- def new(options = {})
112
- tap do
113
- options.each do |key, value|
114
- send("#{key}=", value) if respond_to?("#{key}=")
110
+ def all_jobs
111
+ lock.synchronize do
112
+ HANDLERS[:named].keys
115
113
  end
116
114
  end
117
- end
118
115
 
119
- def work(jobs = nil, runner_count = 1)
120
- self.jobs, self.runner_count = jobs, runner_count
116
+ def job(name, &block)
117
+ lock.synchronize do
118
+ HANDLERS[:named][name] = block
119
+ HANDLERS[:before_named][name] ||= []
120
+ HANDLERS[:after_named][name] ||= []
121
+ HANDLERS[:error_named][name] ||= []
122
+ HANDLERS[:named][name]
123
+ end
124
+ end
121
125
 
122
- default_session.beanstalk # check if we can connect to beanstalk
126
+ def drop(name)
127
+ lock.synchronize do
128
+ HANDLERS[:named].delete(name)
129
+ HANDLERS[:before_named].delete(name)
130
+ HANDLERS[:after_named].delete(name)
131
+ HANDLERS[:error_named].delete(name)
132
+ true
133
+ end
134
+ end
123
135
 
124
- start.join
125
- end
136
+ def handler_for(name)
137
+ lock.synchronize do
138
+ HANDLERS[:named][name]
139
+ end
140
+ end
126
141
 
127
- def start
128
- init_self_pipe!
129
- QUEUE_SIGS.each do |sig|
130
- trap(sig) do
131
- logger.debug "creeper received #{sig}" if $DEBUG
132
- SIG_QUEUE << sig
133
- awaken_creeper
142
+ def before(name = nil, &block)
143
+ if name and name != :each
144
+ lock.synchronize do
145
+ HANDLERS[:before_named][name] << block
146
+ end
147
+ else
148
+ lock.synchronize do
149
+ HANDLERS[:before_each] << block
150
+ end
134
151
  end
135
152
  end
136
153
 
137
- logger.info "creeper starting"
154
+ def before_handlers_for(name)
155
+ lock.synchronize do
156
+ HANDLERS[:before_each] + HANDLERS[:before_named][name]
157
+ end
158
+ end
138
159
 
139
- self
140
- end
160
+ def after(name = nil, &block)
161
+ if name and name != :each
162
+ lock.synchronize do
163
+ HANDLERS[:after_named][name] << block
164
+ end
165
+ else
166
+ lock.synchronize do
167
+ HANDLERS[:after_each] << block
168
+ end
169
+ end
170
+ end
141
171
 
142
- def join
143
- respawn = true
144
- last_check = Time.now
145
-
146
- reset_proc_name
147
- logger.info "creeper process ready"
148
- if ready_pipe
149
- ready_pipe.syswrite($$.to_s)
150
- ready_pipe = ready_pipe.close rescue nil
151
- end
152
- begin
153
- reap_all_runners
154
- case SIG_QUEUE.shift
155
- when nil
156
- # break if soft_quit?
157
- # avoid murdering runners after our master process (or the
158
- # machine) comes out of suspend/hibernation
159
- if (last_check + timeout) >= (last_check = Time.now)
160
- sleep_time = timeout - 1
161
- else
162
- sleep_time = timeout/2.0 + 1
163
- logger.debug("creeper waiting #{sleep_time}s after suspend/hibernation") if $DEBUG
172
+ def after_handlers_for(name)
173
+ lock.synchronize do
174
+ HANDLERS[:after_each] + HANDLERS[:after_named][name]
175
+ end
176
+ end
177
+
178
+ def error(name = nil, &block)
179
+ if name and name != :each
180
+ lock.synchronize do
181
+ HANDLERS[:error_named][name] << block
182
+ end
183
+ else
184
+ lock.synchronize do
185
+ HANDLERS[:error_each] << block
164
186
  end
165
- maintain_runner_count if respawn
166
- logger.debug("creeper sleeping for #{sleep_time}s") if $DEBUG
167
- creeper_sleep(sleep_time)
168
- when :QUIT # graceful shutdown
169
- break
170
- when :TERM, :INT # immediate shutdown
171
- stop(false)
172
- break
173
- when :WINCH
174
- self.runner_count = 0
175
- logger.debug "WINCH: setting runner_count to #{runner_count}" if $DEBUG
176
- when :TTIN
177
- self.runner_count += 1
178
- logger.debug "TTIN: setting runner_count to #{runner_count}" if $DEBUG
179
- when :TTOU
180
- self.runner_count -= 1 if runner_count > 0
181
- logger.debug "TTOU: setting runner_count to #{runner_count}" if $DEBUG
182
187
  end
183
- rescue => e
184
- Creeper.log_exception("creeper loop error", e)
185
- end while true
186
- stop # gracefully shutdown all captains on our way out
187
- logger.info "creeper complete"
188
- end
188
+ end
189
189
 
190
- def stop(graceful = true)
191
- limit = Time.now + patience
192
- kill_all_runners
193
- until (RUNNERS.empty? && GRAVEYARD.empty?) || (n = Time.now) > limit
194
- reap_graveyard(graceful)
195
- sleep(0.1)
190
+ def error_handlers_for(name)
191
+ lock.synchronize do
192
+ HANDLERS[:error_each] + HANDLERS[:error_named][name]
193
+ end
196
194
  end
197
- if n and n > limit
198
- logger.debug "creeper patience exceeded by #{n - limit} seconds (limit #{patience} seconds)" if $DEBUG
195
+
196
+ def finalizer(&block)
197
+ lock.synchronize do
198
+ HANDLERS[:finalizers] << block
199
+ end
200
+ end
201
+
202
+ def finalizers
203
+ lock.synchronize do
204
+ HANDLERS[:finalizers]
205
+ end
199
206
  end
200
- reap_graveyard(false)
201
- logger.debug graceful ? "creeper gracefully stopped" : "creeper hard stopped" if $DEBUG
202
- end
203
207
 
204
- private
208
+ ##
205
209
 
206
- # wait for a signal hander to wake us up and then consume the pipe
207
- def creeper_sleep(sec)
208
- IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return
209
- SELF_PIPE[0].kgio_tryread(11)
210
- end
210
+ ## queue ##
211
211
 
212
- def awaken_creeper
213
- SELF_PIPE[1].kgio_trywrite('.') # wakeup creeper process from select
214
- end
212
+ def enqueue(job, data = {}, options = {})
213
+ # Logger.debug "#{Thread.current[:actor].inspect} Enqueueing #{job.inspect}, #{data.inspect}"#\n#{Celluloid::Actor.all.pretty_inspect}"
214
+ Logger.debug "[#{Thread.current[:actor] ? Thread.current[:actor].subject.number : nil}] Enqueueing #{job.inspect}, #{data.inspect}" if $DEBUG
215
+ enqueue!(job, data, options)
216
+ rescue Beanstalk::NotConnected => e
217
+ disconnected(self, :enqueue, job, data, options)
218
+ end
215
219
 
216
- def init_self_pipe!
217
- SELF_PIPE.each { |io| io.close rescue nil }
218
- SELF_PIPE.replace(Kgio::Pipe.new)
219
- SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
220
- end
220
+ def enqueue!(job, data = {}, options = {})
221
+ priority = options[:priority] || options[:pri] || 65536
222
+ delay = [ 0, options[:delay].to_i ].max
223
+ time_to_run = options[:time_to_run] || options[:ttr] || 120
221
224
 
222
- def kill_all_runners
223
- RUNNERS.each do |thread, worker|
224
- GRAVEYARD[thread] = RUNNERS.delete(thread)
225
+ beanstalk.use job
226
+ beanstalk.put JSON.dump([ job, data ]), priority, delay, time_to_run
225
227
  end
226
- end
227
228
 
228
- def maintain_runner_count
229
- current_runner_count = RUNNERS.size - runner_count
229
+ ##
230
230
 
231
- spawn_missing_runners if current_runner_count < 0
232
- murder_extra_runners if current_runner_count > 0
233
- reap_all_runners
234
- reap_graveyard
235
- end
231
+ ## workers ##
236
232
 
237
- def murder_extra_runners
238
- until RUNNERS.size == runner_count
239
- thread, worker = RUNNERS.shift
240
- if worker.working?
241
- logger.debug "creeper [murder] => soft quit" if $DEBUG
242
- worker.soft_quit = true
243
- GRAVEYARD[thread] = worker
244
- else
245
- logger.debug "creeper [murder] => hard quit" if $DEBUG
246
- thread.kill
247
- thread.join
233
+ def error_work(worker, data, name, job)
234
+ (worker.stopped_at = Time.now).tap do |stopped_at|
235
+ Logger.error "#{worker.prefix} Error in #{worker.time_in_milliseconds}ms #{worker.dump(job, name, data)}"
248
236
  end
249
237
  end
250
- end
251
238
 
252
- def reap_all_runners
253
- RUNNERS.each do |thread, worker|
254
- GRAVEYARD[thread] = worker if not thread.alive?
239
+ def register_worker(worker)
240
+ lock.synchronize do
241
+ number = ((0..(WORKERS.keys.max || 0)+1).to_a - WORKERS.keys).first
242
+ WORKERS[number] = worker.tap do
243
+ worker.number = number
244
+ end
245
+ end
255
246
  end
256
- end
257
247
 
258
- def reap_graveyard(graceful = true)
259
- GRAVEYARD.each do |thread, worker|
260
- if graceful and worker.working?
261
- logger.debug "creeper [graveyard] => soft quit" if $DEBUG
262
- worker.soft_quit = true
263
- else
264
- logger.debug "creeper [graveyard] => hard quit" if $DEBUG
265
- thread.kill rescue nil
266
- thread.join
267
- GRAVEYARD.delete(thread)
248
+ def shutdown_workers
249
+ begin
250
+ soft_shutdown_workers(Creeper.patience_soft)
251
+ rescue Timeout::Error
252
+ begin
253
+ hard_shutdown_workers(Creeper.patience_hard)
254
+ rescue Timeout::Error
255
+ kill_shutdown_workers
256
+ end
257
+ end
258
+ end
259
+
260
+ def start_work(worker, data, name, job)
261
+ (worker.started_at = Time.now).tap do |started_at|
262
+ Logger.info "#{worker.prefix} Working #{Thread.list.count} #{worker.dump(job, name, data)}"
268
263
  end
269
264
  end
270
- end
271
265
 
272
- def spawn_missing_runners
273
- until RUNNERS.size == runner_count
274
- worker = Creeper::Worker.new(jobs: jobs)
275
- thread = worker.start
276
- RUNNERS[thread] = worker
266
+ def stop_work(worker, data, name, job)
267
+ (worker.stopped_at = Time.now).tap do |stopped_at|
268
+ Logger.info "#{worker.prefix} Finished in #{worker.time_in_milliseconds}ms #{worker.dump(job, name, data)}"
269
+ end
277
270
  end
278
- end
279
271
 
280
- def reset_proc_name
281
- proc_name "creeper(#{$$}) [#{runner_count}]"
282
- end
272
+ def unregister_worker(worker, reason = nil)
273
+ reason ||= 'Stopping'
274
+ Logger.info "#{worker.prefix} #{reason}"
275
+ lock.synchronize do
276
+ WORKERS.delete(worker.number)
277
+ end
278
+ end
279
+
280
+ ##
281
+
282
+ protected
283
283
 
284
- def proc_name(tag)
285
- if defined?(Navy) and defined?($officer)
286
- Creeper::START_CTX.merge!(Navy::Admiral::START_CTX)
287
- tag = "(#{$officer.captain.label}) officer[#{$officer.number}] #{tag}"
284
+ def beanstalk_host_and_port(uri_string)
285
+ uri = URI.parse(uri_string)
286
+ raise(BadURL, uri_string) if uri.scheme != 'beanstalk'
287
+ "#{uri.host}:#{uri.port || 11300}"
288
288
  end
289
- $0 = ([
290
- File.basename(Creeper::START_CTX[0]),
291
- tag
292
- ]).concat(Creeper::START_CTX[:argv]).join(' ')
293
- end
294
289
 
295
- ##
290
+ def disconnected(target, method, *args, &block)
291
+ Thread.current[:beanstalk_connection_retries] ||= 0
292
+
293
+ if Thread.current[:beanstalk_connection_retries] >= retry_count
294
+ Logger.error "Unable to connect to beanstalk after #{Thread.current[:beanstalk_connection_retries]} attempts"
295
+ Thread.current[:beanstalk_connection_retries] = 0
296
+ return false
297
+ end
298
+
299
+ disconnect
300
+
301
+ Thread.current[:beanstalk_connection_retries] += 1
302
+
303
+ sleep Thread.current[:beanstalk_connection_retries] * 2
304
+
305
+ target.send(method, *args, &block)
306
+ end
307
+
308
+ def soft_shutdown_workers(timeout)
309
+ Timeout.timeout(timeout) do
310
+ actors = Celluloid::Actor.all
311
+ Logger.info "Gracefully stopping #{actors.size} actors..." if actors.size > 0
312
+
313
+ # Attempt to shut down the supervision tree, if available
314
+ Celluloid::Supervisor.root.terminate if Celluloid::Supervisor.root
315
+
316
+ # Actors cannot self-terminate, you must do it for them
317
+ starts = working_actors.map do |actor|
318
+ begin
319
+ if actor.alive?
320
+ actor.stop! # fire and forget for those already working
321
+ actor.future(:start, true) # ensures that the mailbox is cleared out
322
+ end
323
+ rescue Celluloid::DeadActorError, Celluloid::MailboxError
324
+ end
325
+ end.compact
326
+
327
+ starts.each do |start|
328
+ begin
329
+ start.value
330
+ rescue Celluloid::DeadActorError, Celluloid::MailboxError
331
+ end
332
+ end
333
+
334
+ Logger.info "Graceful stop completed cleanly"
335
+ end
336
+ end
337
+
338
+ def hard_shutdown_workers(timeout)
339
+ Timeout.timeout(timeout) do
340
+ actors = Celluloid::Actor.all
341
+ Logger.info "Terminating #{actors.size} actors..." if actors.size > 0
342
+
343
+ # Attempt to shut down the supervision tree, if available
344
+ Celluloid::Supervisor.root.terminate if Celluloid::Supervisor.root
345
+
346
+ pool_managers.each do |pool_manager|
347
+ begin
348
+ pool_manager.terminate
349
+ rescue Celluloid::DeadActorError, Celluloid::MailboxError
350
+ end
351
+ end
352
+
353
+ # Actors cannot self-terminate, you must do it for them
354
+ working_actors.each do |actor|
355
+ begin
356
+ actor.terminate
357
+ rescue Celluloid::DeadActorError, Celluloid::MailboxError
358
+ end
359
+ end
360
+
361
+ Logger.info "Termination completed cleanly"
362
+ end
363
+ end
364
+
365
+ def kill_shutdown_workers
366
+ actors = Celluloid::Actor.all
367
+ Logger.info "Killing #{actors.size} actors..." if actors.size > 0
368
+
369
+ # Attempt to shut down the supervision tree, if available
370
+ Celluloid::Supervisor.root.kill if Celluloid::Supervisor.root
371
+
372
+ # Actors cannot self-terminate, you must do it for them
373
+ Celluloid::Actor.all.each do |actor|
374
+ begin
375
+ actor.kill
376
+ actor.join
377
+ rescue Celluloid::DeadActorError, Celluloid::MailboxError
378
+ end
379
+ end
380
+
381
+ Logger.info "Killing completed cleanly"
382
+ end
383
+
384
+ def pool_managers
385
+ Celluloid::Actor.all.tap do |actors|
386
+ actors.keep_if do |actor|
387
+ actor.is_a?(Celluloid::PoolManager) rescue false
388
+ end
389
+ end
390
+ end
391
+
392
+ def working_actors
393
+ Celluloid::Actor.all.tap do |actors|
394
+ actors.delete_if do |actor|
395
+ actor.is_a?(Celluloid::PoolManager) rescue false
396
+ end
397
+ end
398
+ end
399
+
400
+ end
296
401
 
297
402
  end
403
+
404
+ # require 'creeper/creep'
405
+ require 'creeper/logger'