creeper 0.0.5 → 1.0.0

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