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.
@@ -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