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 +2 -0
- data/LICENSE +1 -1
- data/README.md +16 -89
- data/Rakefile +0 -4
- data/creeper.gemspec +7 -8
- data/lib/creeper.rb +332 -224
- data/lib/creeper/celluloid_ext.rb +42 -0
- data/lib/creeper/creep.rb +10 -16
- data/lib/creeper/logger.rb +37 -0
- data/lib/creeper/version.rb +1 -1
- data/lib/creeper/worker.rb +260 -102
- metadata +43 -13
- data/lib/creeper/session.rb +0 -141
@@ -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
|
data/lib/creeper/creep.rb
CHANGED
@@ -1,30 +1,24 @@
|
|
1
1
|
module Creeper
|
2
2
|
module Creep
|
3
3
|
|
4
|
-
def
|
5
|
-
Creeper.
|
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
|
10
|
-
|
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
|
19
|
-
|
12
|
+
def before(name = nil, &block)
|
13
|
+
Creeper.before(name, &block)
|
20
14
|
end
|
21
15
|
|
22
|
-
def
|
23
|
-
|
16
|
+
def after(name = nil, &block)
|
17
|
+
Creeper.after(name, &block)
|
24
18
|
end
|
25
19
|
|
26
|
-
def error(&block)
|
27
|
-
|
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
|
data/lib/creeper/version.rb
CHANGED
data/lib/creeper/worker.rb
CHANGED
@@ -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
|
-
|
15
|
+
def self.work(jobs = nil, size = 2)
|
5
16
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
17
|
+
options = {
|
18
|
+
size: size,
|
19
|
+
args: [jobs, size]
|
20
|
+
}
|
10
21
|
|
11
|
-
|
12
|
-
Creeper.logger
|
13
|
-
end
|
22
|
+
Creeper.worker_pool = Creeper::Worker.pool(options)
|
14
23
|
|
15
|
-
|
16
|
-
|
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
|
20
|
-
|
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
|
-
|
24
|
-
|
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
|
28
|
-
|
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
|
-
|
34
|
-
|
112
|
+
def number_format
|
113
|
+
"%#{pool_size.to_s.length}d"
|
114
|
+
end
|
35
115
|
|
36
|
-
def
|
37
|
-
|
116
|
+
def time_in_milliseconds
|
117
|
+
((stopped_at - started_at).to_f * 1000).to_i
|
38
118
|
end
|
39
119
|
|
40
|
-
|
120
|
+
def started_at
|
121
|
+
Thread.current[:creeper_started_at]
|
122
|
+
end
|
41
123
|
|
42
|
-
def
|
43
|
-
|
124
|
+
def started_at=(started_at)
|
125
|
+
Thread.current[:creeper_started_at] = started_at
|
44
126
|
end
|
45
127
|
|
46
|
-
def
|
47
|
-
|
128
|
+
def stopped_at
|
129
|
+
Thread.current[:creeper_stopped_at]
|
48
130
|
end
|
49
|
-
alias :soft_quit? :soft_quit
|
50
131
|
|
51
|
-
def
|
52
|
-
|
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
|
-
|
139
|
+
Creeper.beanstalk
|
60
140
|
end
|
61
141
|
|
62
|
-
def
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
69
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
76
|
-
|
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
|
-
|
183
|
+
exit if stopped?
|
80
184
|
|
81
|
-
|
185
|
+
Thread.current[:creeper_working] = true
|
82
186
|
|
83
|
-
|
84
|
-
|
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
|
-
|
87
|
-
|
212
|
+
|
213
|
+
ensure
|
214
|
+
Creeper.disconnect
|
88
215
|
end
|
89
216
|
|
90
|
-
def
|
91
|
-
|
92
|
-
loop { work_one_job }
|
217
|
+
def stop
|
218
|
+
Thread.current[:creeper_stopped] = true
|
93
219
|
end
|
94
220
|
|
95
|
-
def
|
96
|
-
|
221
|
+
def work(job)
|
222
|
+
|
223
|
+
exit if stopped?
|
97
224
|
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
106
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
117
|
-
|
257
|
+
job.release rescue nil
|
258
|
+
Creeper.unregister_worker(self)
|
118
259
|
rescue => e
|
119
|
-
|
260
|
+
|
120
261
|
job.bury rescue nil
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
133
|
-
|
134
|
-
|
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
|
-
|
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
|
-
|
295
|
+
## flags ##
|
146
296
|
|
147
|
-
def
|
148
|
-
|
149
|
-
|
297
|
+
def prepared?
|
298
|
+
Thread.current[:creeper_prepared] == true
|
299
|
+
end
|
150
300
|
|
151
|
-
|
152
|
-
|
301
|
+
def stopped?
|
302
|
+
Thread.current[:creeper_stopped] == true || Creeper.shutdown?
|
153
303
|
end
|
154
304
|
|
155
|
-
def
|
156
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|