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 +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
|