creeper 0.0.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,42 @@
1
+ module Celluloid
2
+ class Actor
3
+
4
+ def handle_crash(exception)
5
+ prefix = (@subject.respond_to?(:prefix) rescue nil) ? ("#{@subject.prefix} " rescue nil) : nil
6
+ Logger.crash("#{prefix}#{@subject.class} crashed!", exception)
7
+ shutdown ExitEvent.new(@proxy, exception)
8
+ rescue => ex
9
+ Logger.crash("#{@subject.class}: ERROR HANDLER CRASHED!", ex)
10
+ end
11
+
12
+ end
13
+ end
14
+
15
+ module Celluloid
16
+ class PoolManager
17
+
18
+ # ensure that the provisioned worker is alive to prevent PoolManager from dying
19
+ def execute(method, *args, &block)
20
+ worker = provision_worker
21
+
22
+ begin
23
+ worker._send_ method, *args, &block
24
+ rescue Celluloid::DeadActorError, Celluloid::MailboxError
25
+ execute(method, *args, &block)
26
+ ensure
27
+ @idle << worker if worker && worker.alive?
28
+ end
29
+ end
30
+
31
+ def crash_handler(actor, reason)
32
+ return unless reason # don't restart workers that exit cleanly
33
+ index = @idle.rindex(actor) # replace the old actor if possible
34
+ if index
35
+ @idle[index] = @worker_class.new_link(*@args)
36
+ else
37
+ @idle << @worker_class.new_link(*@args)
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -1,30 +1,24 @@
1
1
  module Creeper
2
2
  module Creep
3
3
 
4
- def clear!
5
- Creeper.default_session.disconnect if Creeper.instance_variable_defined?(:@default_session) and Creeper.instance_variable_get(:@default_session)
6
- Creeper.instance_variable_set(:@default_session, nil)
4
+ def enqueue(job, data = {}, options = {})
5
+ Creeper.enqueue(job, data, options)
7
6
  end
8
7
 
9
- def default_session
10
- return Creeper.instance_variable_get(:@default_session) if Creeper.instance_variable_defined?(:@default_session)
11
- Creeper.instance_variable_set(:@default_session, Creeper::Session.new)
12
- end
13
-
14
- def enqueue(job, args = {}, opts = {})
15
- default_session.enqueue(job, args, opts)
8
+ def job(name, &block)
9
+ Creeper.job(name, &block)
16
10
  end
17
11
 
18
- def job(name, &block)
19
- default_session.job(name, &block)
12
+ def before(name = nil, &block)
13
+ Creeper.before(name, &block)
20
14
  end
21
15
 
22
- def before(&block)
23
- default_session.before(&block)
16
+ def after(name = nil, &block)
17
+ Creeper.after(name, &block)
24
18
  end
25
19
 
26
- def error(&block)
27
- default_session.error(&block)
20
+ def error(name = nil, &block)
21
+ Creeper.error(name, &block)
28
22
  end
29
23
 
30
24
  end
@@ -0,0 +1,37 @@
1
+ module Creeper
2
+ module Logger
3
+ module_function
4
+
5
+ # Send a debug message
6
+ def debug(string)
7
+ Creeper.logger.debug(string) if Creeper.logger
8
+ end
9
+
10
+ # Send a info message
11
+ def info(string)
12
+ Creeper.logger.info(string) if Creeper.logger
13
+ end
14
+
15
+ # Send a warning message
16
+ def warn(string)
17
+ Creeper.logger.warn(string) if Creeper.logger
18
+ end
19
+
20
+ # Send an error message
21
+ def error(string)
22
+ Creeper.logger.error(string) if Creeper.logger
23
+ end
24
+
25
+ # Handle a crash
26
+ def crash(string, exception)
27
+ string << "\n" << format_exception(exception)
28
+ error string
29
+ end
30
+
31
+ # Format an exception message
32
+ def format_exception(exception)
33
+ str = "#{exception.class}: #{exception.to_s}\n"
34
+ str << exception.backtrace.join("\n")
35
+ end
36
+ end
37
+ end
@@ -1,3 +1,3 @@
1
1
  module Creeper
2
- VERSION = "0.0.5"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -1,173 +1,331 @@
1
+ require 'creeper'
2
+
3
+ require 'celluloid'
4
+ require 'creeper/celluloid_ext'
5
+ # require 'em-jack'
6
+
7
+ Creeper.logger = Celluloid.logger
8
+
1
9
  module Creeper
10
+
11
+ at_exit { shutdown_workers }
12
+
2
13
  class Worker
3
14
 
4
- ## utilities ##
15
+ def self.work(jobs = nil, size = 2)
5
16
 
6
- class NoJobsDefined < RuntimeError; end
7
- class NoSuchJob < RuntimeError; end
8
- class JobTimeout < RuntimeError; end
9
- class BadURL < RuntimeError; end
17
+ options = {
18
+ size: size,
19
+ args: [jobs, size]
20
+ }
10
21
 
11
- def logger
12
- Creeper.logger
13
- end
22
+ Creeper.worker_pool = Creeper::Worker.pool(options)
14
23
 
15
- def log_exception(*args)
16
- Creeper.log_exception(*args)
24
+ begin
25
+ trap(:INT) { Creeper.shutdown = true }
26
+ trap(:TERM) { Creeper.shutdown = true }
27
+ trap(:QUIT) { Creeper.shutdown = true }
28
+ Creeper.worker_pool.start
29
+ end until Creeper.shutdown?
30
+
31
+ exit
17
32
  end
18
33
 
19
- def error_logger
20
- Creeper.error_logger
34
+ # def self.em_work(jobs = nil, size = 2)
35
+
36
+ # options = {
37
+ # size: size,
38
+ # args: [jobs]
39
+ # }
40
+
41
+ # tubes = jobs_for(jobs)
42
+
43
+ # Creeper.worker_pool = Creeper::Worker.pool(options)
44
+
45
+ # sleep 1
46
+
47
+ # EM.run do
48
+ # trap(:INT) { Creeper.shutdown = true; EM.stop }
49
+ # trap(:TERM) { Creeper.shutdown = true; EM.stop }
50
+ # trap(:QUIT) { Creeper.shutdown = true; EM.stop }
51
+
52
+ # jack = EMJack::Connection.new(Creeper.beanstalk_url)
53
+
54
+ # reserve_loop = ->(timeout) do
55
+ # r = jack.reserve(timeout)
56
+ # r.callback do |job|
57
+ # Creeper.worker_pool.work! job
58
+ # EM.next_tick { reserve_loop.call(timeout) }
59
+ # end
60
+ # r.errback do
61
+ # EM.next_tick { reserve_loop.call(timeout) }
62
+ # end
63
+ # end
64
+
65
+ # watch_tubes = ->(list) do
66
+ # if list.empty?
67
+ # reserve_loop.call(Creeper.reserve_timeout)
68
+ # else
69
+ # w = jack.watch(list.shift)
70
+ # w.callback do
71
+ # watch_tubes.call(list)
72
+ # end
73
+ # end
74
+ # end
75
+
76
+ # watch_tubes.call(tubes)
77
+
78
+ # end
79
+
80
+ # end
81
+
82
+ def self.jobs_for(jobs = nil)
83
+ case jobs
84
+ when :all, nil
85
+ Creeper.all_jobs
86
+ else
87
+ Array(jobs)
88
+ end
21
89
  end
22
90
 
23
- def log(msg)
24
- logger.info(msg)
91
+ include Celluloid
92
+
93
+ attr_accessor :number
94
+ attr_reader :jobs, :pool_size
95
+
96
+ def initialize(jobs = nil, pool_size = 2)
97
+ @jobs = self.class.jobs_for(jobs)
98
+ @pool_size = pool_size
99
+
100
+ Creeper.register_worker(self)
101
+ Logger.info "#{prefix} Working #{self.jobs.size} jobs: [ #{self.jobs.join(' ')} ]"
25
102
  end
26
103
 
27
- def log_error(msg)
28
- error_logger.error(msg)
104
+ def dump(job, name = nil, data = nil)
105
+ "#{name.inspect rescue nil} { data: #{(data.inspect rescue nil)}, job: #{(job.inspect rescue nil)} }"
29
106
  end
30
107
 
31
- ##
108
+ def prefix
109
+ "[#{number_format % number} - #{'%x' % Thread.current.object_id}]"
110
+ end
32
111
 
33
- attr_reader :options, :session, :thread
34
- attr_accessor :soft_quit, :working
112
+ def number_format
113
+ "%#{pool_size.to_s.length}d"
114
+ end
35
115
 
36
- def initialize(options = {})#jobs = nil, parent_session = , session = nil)
37
- @options = options
116
+ def time_in_milliseconds
117
+ ((stopped_at - started_at).to_f * 1000).to_i
38
118
  end
39
119
 
40
- ### config ###
120
+ def started_at
121
+ Thread.current[:creeper_started_at]
122
+ end
41
123
 
42
- def session
43
- @session ||= Creeper::Session.new(options[:parent_session] || Creeper.default_session)
124
+ def started_at=(started_at)
125
+ Thread.current[:creeper_started_at] = started_at
44
126
  end
45
127
 
46
- def soft_quit
47
- @soft_quit ||= false
128
+ def stopped_at
129
+ Thread.current[:creeper_stopped_at]
48
130
  end
49
- alias :soft_quit? :soft_quit
50
131
 
51
- def working
52
- @working ||= false
132
+ def stopped_at=(stopped_at)
133
+ Thread.current[:creeper_stopped_at] = stopped_at
53
134
  end
54
- alias :working? :working
55
135
 
56
- ###
136
+ ## beanstalk ##
57
137
 
58
138
  def beanstalk
59
- session.beanstalk
139
+ Creeper.beanstalk
60
140
  end
61
141
 
62
- def clear!
63
- @soft_quit = false
64
- @working = false
65
- @session = nil
142
+ def ignore(tube)
143
+ beanstalk.ignore(tube)
144
+ rescue Beanstalk::NotConnected => e
145
+ disconnected(self, :ignore, tube) || raise
66
146
  end
67
147
 
68
- def prepare
69
- raise NoJobsDefined if session.handlers.empty?
148
+ def list_tubes_watched(cached = false)
149
+ beanstalk.list_tubes_watched
150
+ rescue Beanstalk::NotConnected => e
151
+ disconnected(self, :list_tubes_watched, cached) || raise
152
+ end
70
153
 
71
- jobs = session.all_jobs if options[:jobs] == :all
72
- jobs ||= options[:jobs] ? [*options[:jobs]] : session.all_jobs
73
- jobs = session.all_jobs if jobs.empty?
154
+ def reserve(timeout = nil)
155
+ beanstalk.reserve(timeout)
156
+ rescue Beanstalk::NotConnected => e
157
+ disconnected(self, :reserve, timeout) || raise
158
+ end
74
159
 
75
- jobs.each do |job|
76
- raise(NoSuchJob, job) unless session.handlers.has_key?(job)
160
+ def watch(tube)
161
+ beanstalk.watch(tube)
162
+ rescue Beanstalk::NotConnected => e
163
+ disconnected(self, :watch, tube) || raise
164
+ end
165
+
166
+ ##
167
+
168
+ ## work ##
169
+
170
+ def start(short_circuit = false)
171
+ return false if short_circuit
172
+ exit if stopped?
173
+ return true if working?
174
+ prepare if not prepared?
175
+
176
+ begin
177
+ job = reserve Creeper.reserve_timeout
178
+ rescue Beanstalk::TimedOut
179
+ Logger.debug "#{prefix} Back to the unemployment line" if $DEBUG
180
+ return false
77
181
  end
78
182
 
79
- logger.info "Working #{jobs.size} jobs: [ #{jobs.join(' ')} ]"
183
+ exit if stopped?
80
184
 
81
- jobs.each { |job| beanstalk.watch(job) }
185
+ Thread.current[:creeper_working] = true
82
186
 
83
- beanstalk.list_tubes_watched.each do |server, tubes|
84
- tubes.each { |tube| beanstalk.ignore(tube) unless jobs.include?(tube) }
187
+ Logger.debug "#{prefix} Got #{job.inspect}" if $DEBUG
188
+
189
+ work! job # asynchronously go to work
190
+ rescue SystemExit => e
191
+ job.release rescue nil
192
+ Creeper.unregister_worker(self)
193
+ rescue => e
194
+ job.release rescue nil
195
+ Creeper.unregister_worker(self, "start loop error")
196
+ raise
197
+ end
198
+
199
+ def finalize
200
+ Creeper.finalizers.each do |finalizer|
201
+ begin
202
+ case finalizer.arity
203
+ when 1
204
+ finalizer.call(self)
205
+ else
206
+ finalizer.call
207
+ end
208
+ rescue => e
209
+ Logger.crash "#{prefix} finalizer error", e
210
+ end
85
211
  end
86
- rescue Beanstalk::NotConnected => e
87
- log_exception("worker[#{@thread.inspect}] failed beanstalk connection", e)
212
+
213
+ ensure
214
+ Creeper.disconnect
88
215
  end
89
216
 
90
- def work
91
- prepare
92
- loop { work_one_job }
217
+ def stop
218
+ Thread.current[:creeper_stopped] = true
93
219
  end
94
220
 
95
- def work_one_job
96
- stop if soft_quit?
221
+ def work(job)
222
+
223
+ exit if stopped?
97
224
 
98
- job = beanstalk.reserve
99
- name, args = JSON.parse job.body
100
- log_job_begin(name, args)
101
- handler = session.handlers[name]
102
- raise(NoSuchJob, name) unless handler
225
+ name, data = JSON.parse(job.body)
226
+
227
+ Creeper.start_work(self, data, name, job)
103
228
 
104
229
  begin
105
- session.before_handlers.each do |block|
106
- block.call(name)
230
+ Creeper.before_handlers_for(name).each do |handler|
231
+ process(handler, data, name, job)
232
+ end
233
+
234
+ Creeper.handler_for(name).tap do |handler|
235
+ process(handler, data, name, job)
236
+ end
237
+
238
+ Creeper.after_handlers_for(name).each do |handler|
239
+ process(handler, data, name, job)
107
240
  end
108
- handler.call(args)
109
241
  end
110
242
 
111
243
  job.delete
112
- log_job_end(name, args)
244
+
245
+ Creeper.stop_work(self, data, name, job)
246
+
247
+ # start! unless stopped? or EM.reactor_running?
248
+ start! unless stopped? # continue processing, even when end of links is reached
249
+
113
250
  rescue Beanstalk::NotConnected => e
114
- log_exception("worker[#{@thread.inspect}] failed beanstalk connection", e)
251
+ disconnected(self, :work, job) || begin
252
+ job.release rescue nil
253
+ Creeper.unregister_worker(self)
254
+ raise
255
+ end
115
256
  rescue SystemExit => e
116
- log_exception("worker[#{@thread.inspect}] exit", e)
117
- raise
257
+ job.release rescue nil
258
+ Creeper.unregister_worker(self)
118
259
  rescue => e
119
- log_exception("worker[#{@thread.inspect}] loop error", e)
260
+
120
261
  job.bury rescue nil
121
- args ||= []
122
- log_job_end(name, args, 'failed') if @job_begun
123
- if session.error_handler
124
- if session.error_handler.arity == 1
125
- session.error_handler.call(e)
126
- else
127
- session.error_handler.call(e, name, args)
262
+
263
+ Creeper.error_work(self, data, name, job)
264
+
265
+ begin
266
+ Creeper.error_handlers_for(name).each do |handler|
267
+ process(handler, data, name, job)
128
268
  end
129
269
  end
270
+
271
+ Creeper.unregister_worker(self, "work loop error, burying #{dump(job, name, data)}")
272
+
273
+ raise
274
+ ensure
275
+ Thread.current[:creeper_started_at] = nil
276
+ Thread.current[:creeper_stopped_at] = nil
277
+ Thread.current[:creeper_working] = false
130
278
  end
131
279
 
132
- def start
133
- @thread = Thread.new do
134
- work
280
+ def process(handler, data, name, job)
281
+ case handler.arity
282
+ when 3
283
+ handler.call(data, name, job)
284
+ when 2
285
+ handler.call(data, name)
286
+ when 1
287
+ handler.call(data)
288
+ else
289
+ handler.call
135
290
  end
136
291
  end
137
292
 
138
- def stop
139
- logger.info "worker dying: (current=#{Thread.current.inspect}#{@thread.inspect}"
140
- session.disconnect
141
- sleep 1
142
- @thread.kill
143
- end
293
+ ##
144
294
 
145
- protected
295
+ ## flags ##
146
296
 
147
- def log_job_begin(name, args)
148
- @working = true
149
- args_flat = flatten_args(args)
297
+ def prepared?
298
+ Thread.current[:creeper_prepared] == true
299
+ end
150
300
 
151
- log [ "Working", name, args_flat ].join(' ')
152
- @job_begun = Time.now
301
+ def stopped?
302
+ Thread.current[:creeper_stopped] == true || Creeper.shutdown?
153
303
  end
154
304
 
155
- def log_job_end(name, args, failed=false)
156
- @working = false
157
- ellapsed = Time.now - @job_begun
158
- ms = (ellapsed.to_f * 1000).to_i
159
- args_flat = flatten_args(args)
160
- log [ "Finished #{name} in #{ms}ms #{failed ? ' (failed)' : ''}", args_flat ].join(' ')
305
+ def working?
306
+ Thread.current[:creeper_working] == true
161
307
  end
162
308
 
163
- def flatten_args(args)
164
- unless args.empty?
165
- '(' + args.inject([]) do |accum, (key,value)|
166
- accum << "#{key}=#{value}"
167
- end.join(' ') + ')'
168
- else
169
- ''
309
+ ##
310
+
311
+ protected
312
+
313
+ def disconnected(target, method, *args, &block)
314
+ Creeper.send(:disconnected, target, method, *args, &block)
315
+ end
316
+
317
+ def prepare
318
+ jobs.each do |job|
319
+ watch(job)
170
320
  end
321
+
322
+ list_tubes_watched.each do |server, tubes|
323
+ tubes.each do |tube|
324
+ ignore(tube) unless jobs.include?(tube)
325
+ end
326
+ end
327
+
328
+ Thread.current[:creeper_prepared] = true
171
329
  end
172
330
 
173
331
  end