creeper 0.0.2 → 0.0.3
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/creeper.gemspec +5 -2
- data/lib/creeper.rb +242 -97
- data/lib/creeper/session.rb +142 -0
- data/lib/creeper/version.rb +1 -1
- data/lib/creeper/worker.rb +139 -134
- data/spec/lib/creeper/session_spec.rb +15 -0
- data/spec/lib/creeper/worker_spec.rb +21 -0
- data/spec/{creeper_spec.rb → lib/creeper_spec.rb} +9 -19
- data/spec/spec_helper.rb +1 -0
- metadata +28 -9
- data/spec/worker_spec.rb +0 -16
data/creeper.gemspec
CHANGED
@@ -15,9 +15,12 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = Creeper::VERSION
|
17
17
|
|
18
|
-
gem.
|
18
|
+
gem.add_development_dependency 'pry'
|
19
19
|
gem.add_development_dependency 'rspec'
|
20
|
-
gem.
|
20
|
+
# gem.add_development_dependency 'stalker'
|
21
|
+
|
22
|
+
gem.add_dependency 'beanstalk-client'
|
23
|
+
gem.add_dependency 'kgio'
|
21
24
|
|
22
25
|
end
|
23
26
|
|
data/lib/creeper.rb
CHANGED
@@ -1,161 +1,306 @@
|
|
1
1
|
require 'beanstalk-client'
|
2
|
-
|
3
|
-
require "creeper/worker"
|
2
|
+
|
4
3
|
require 'json'
|
5
4
|
require 'uri'
|
6
5
|
require 'timeout'
|
7
6
|
|
8
|
-
|
7
|
+
require 'fcntl'
|
8
|
+
require 'etc'
|
9
|
+
require 'stringio'
|
10
|
+
require 'kgio'
|
11
|
+
|
12
|
+
require 'logger'
|
13
|
+
|
14
|
+
require 'creeper/version'
|
15
|
+
require 'creeper/session'
|
16
|
+
require 'creeper/worker'
|
17
|
+
|
18
|
+
$stdout.sync = $stderr.sync = true
|
19
|
+
$stdin.binmode
|
20
|
+
$stdout.binmode
|
21
|
+
$stderr.binmode
|
9
22
|
|
10
23
|
module Creeper
|
24
|
+
|
25
|
+
# These hashes map Threads to Workers
|
26
|
+
RUNNERS = {}
|
27
|
+
GRAVEYARD = {}
|
28
|
+
|
29
|
+
SELF_PIPE = []
|
30
|
+
|
31
|
+
# signal queue used for self-piping
|
32
|
+
SIG_QUEUE = []
|
33
|
+
|
34
|
+
# list of signals we care about and trap in Creeper
|
35
|
+
QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :TTIN, :TTOU ]
|
36
|
+
|
37
|
+
START_CTX = {
|
38
|
+
:argv => ARGV.map { |arg| arg.dup },
|
39
|
+
0 => $0.dup,
|
40
|
+
}
|
41
|
+
START_CTX[:cwd] = begin
|
42
|
+
a = File.stat(pwd = ENV['PWD'])
|
43
|
+
b = File.stat(Dir.pwd)
|
44
|
+
a.ino == b.ino && a.dev == b.dev ? pwd : Dir.pwd
|
45
|
+
rescue
|
46
|
+
Dir.pwd
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_accessor :logger, :error_logger
|
50
|
+
attr_accessor :jobs, :patience, :runner_count, :soft_quit, :timeout
|
51
|
+
|
11
52
|
extend self
|
12
53
|
|
13
|
-
|
14
|
-
|
15
|
-
|
54
|
+
## utilities ##
|
55
|
+
|
56
|
+
def logger
|
57
|
+
@logger ||= Logger.new($stdout)
|
16
58
|
end
|
17
59
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
beanstalk.put [ job, args ].to_json, pri, delay, ttr
|
24
|
-
rescue Beanstalk::NotConnected => e
|
25
|
-
failed_connection(e)
|
60
|
+
def log_exception(prefix, exc, logger = error_logger)
|
61
|
+
message = exc.message
|
62
|
+
message = message.dump if /[[:cntrl:]]/ =~ message
|
63
|
+
logger.error "#{prefix}: #{message} (#{exc.class})"
|
64
|
+
exc.backtrace.each { |line| logger.error(line) }
|
26
65
|
end
|
27
66
|
|
28
|
-
def
|
29
|
-
|
30
|
-
@@handlers[j] = block
|
67
|
+
def error_logger
|
68
|
+
@error_logger ||= Logger.new($stderr)
|
31
69
|
end
|
32
70
|
|
33
|
-
|
34
|
-
|
35
|
-
|
71
|
+
##
|
72
|
+
|
73
|
+
## main process ##
|
74
|
+
|
75
|
+
### config ###
|
76
|
+
|
77
|
+
def patience
|
78
|
+
@patience ||= 60
|
36
79
|
end
|
37
80
|
|
38
|
-
def
|
39
|
-
|
81
|
+
def runner_count
|
82
|
+
@runner_count ||= 1
|
40
83
|
end
|
41
84
|
|
42
|
-
def
|
43
|
-
|
85
|
+
def runner_count=(value)
|
86
|
+
(@runner_count = value).tap do
|
87
|
+
reset_proc_name
|
88
|
+
end
|
44
89
|
end
|
45
90
|
|
46
|
-
def soft_quit
|
47
|
-
|
91
|
+
def soft_quit
|
92
|
+
@soft_quit ||= false
|
48
93
|
end
|
94
|
+
alias :soft_quit? :soft_quit
|
49
95
|
|
50
96
|
def soft_quit=(soft_quit)
|
51
|
-
|
97
|
+
(@soft_quit = soft_quit).tap do
|
98
|
+
awaken_creeper if soft_quit?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def timeout
|
103
|
+
@timeout ||= 30
|
104
|
+
end
|
105
|
+
|
106
|
+
###
|
107
|
+
|
108
|
+
def work(jobs = nil, runner_count = 1)
|
109
|
+
self.jobs, self.runner_count = jobs, runner_count
|
110
|
+
|
111
|
+
default_session.beanstalk # check if we can connect to beanstalk
|
112
|
+
|
113
|
+
start.join
|
52
114
|
end
|
53
115
|
|
54
|
-
def
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
116
|
+
def start
|
117
|
+
init_self_pipe!
|
118
|
+
QUEUE_SIGS.each do |sig|
|
119
|
+
trap(sig) do
|
120
|
+
logger.debug "creeper received #{sig}" if $DEBUG
|
121
|
+
SIG_QUEUE << sig
|
122
|
+
awaken_creeper
|
59
123
|
end
|
60
|
-
running << {thread: t, worker: w}
|
61
124
|
end
|
62
125
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
126
|
+
logger.info "creeper starting"
|
127
|
+
|
128
|
+
self
|
129
|
+
end
|
130
|
+
|
131
|
+
def join
|
132
|
+
respawn = true
|
133
|
+
last_check = Time.now
|
134
|
+
|
135
|
+
reset_proc_name
|
136
|
+
logger.info "creeper process ready"
|
137
|
+
begin
|
138
|
+
reap_all_runners
|
139
|
+
case SIG_QUEUE.shift
|
140
|
+
when nil
|
141
|
+
# break if soft_quit?
|
142
|
+
# avoid murdering runners after our master process (or the
|
143
|
+
# machine) comes out of suspend/hibernation
|
144
|
+
if (last_check + timeout) >= (last_check = Time.now)
|
145
|
+
sleep_time = timeout - 1
|
146
|
+
else
|
147
|
+
sleep_time = timeout/2.0 + 1
|
148
|
+
logger.debug("creeper waiting #{sleep_time}s after suspend/hibernation") if $DEBUG
|
71
149
|
end
|
150
|
+
maintain_runner_count if respawn
|
151
|
+
logger.debug("creeper sleeping for #{sleep_time}s") if $DEBUG
|
152
|
+
creeper_sleep(sleep_time)
|
153
|
+
when :QUIT # graceful shutdown
|
154
|
+
break
|
155
|
+
when :TERM, :INT # immediate shutdown
|
156
|
+
stop(false)
|
157
|
+
break
|
158
|
+
when :WINCH
|
159
|
+
self.runner_count = 0
|
160
|
+
logger.debug "WINCH: setting runner_count to #{runner_count}" if $DEBUG
|
161
|
+
when :TTIN
|
162
|
+
self.runner_count += 1
|
163
|
+
logger.debug "TTIN: setting runner_count to #{runner_count}" if $DEBUG
|
164
|
+
when :TTOU
|
165
|
+
self.runner_count -= 1 if runner_count > 0
|
166
|
+
logger.debug "TTOU: setting runner_count to #{runner_count}" if $DEBUG
|
72
167
|
end
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
168
|
+
rescue => e
|
169
|
+
Creeper.log_exception("creeper loop error", e)
|
170
|
+
end while true
|
171
|
+
stop # gracefully shutdown all captains on our way out
|
172
|
+
logger.info "creeper complete"
|
173
|
+
end
|
174
|
+
|
175
|
+
def stop(graceful = true)
|
176
|
+
limit = Time.now + patience
|
177
|
+
kill_all_runners
|
178
|
+
until (RUNNERS.empty? && GRAVEYARD.empty?) || (n = Time.now) > limit
|
179
|
+
reap_graveyard(graceful)
|
180
|
+
sleep(0.1)
|
83
181
|
end
|
84
|
-
|
85
|
-
|
182
|
+
if n and n > limit
|
183
|
+
logger.debug "creeper patience exceeded by #{n - limit} seconds (limit #{patience} seconds)" if $DEBUG
|
86
184
|
end
|
87
|
-
|
185
|
+
reap_graveyard(false)
|
186
|
+
logger.debug graceful ? "creeper gracefully stopped" : "creeper hard stopped" if $DEBUG
|
88
187
|
end
|
89
188
|
|
90
|
-
|
91
|
-
log_error exception_message(e)
|
92
|
-
log_error "*** Failed connection to #{beanstalk_url}"
|
93
|
-
log_error "*** Check that beanstalkd is running (or set a different BEANSTALK_URL)"
|
94
|
-
exit 1
|
95
|
-
end
|
189
|
+
private
|
96
190
|
|
97
|
-
|
98
|
-
|
191
|
+
# wait for a signal hander to wake us up and then consume the pipe
|
192
|
+
def creeper_sleep(sec)
|
193
|
+
IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return
|
194
|
+
SELF_PIPE[0].kgio_tryread(11)
|
99
195
|
end
|
100
196
|
|
101
|
-
def
|
102
|
-
|
197
|
+
def awaken_creeper
|
198
|
+
SELF_PIPE[1].kgio_trywrite('.') # wakeup creeper process from select
|
103
199
|
end
|
104
200
|
|
105
|
-
def
|
106
|
-
|
201
|
+
def init_self_pipe!
|
202
|
+
SELF_PIPE.each { |io| io.close rescue nil }
|
203
|
+
SELF_PIPE.replace(Kgio::Pipe.new)
|
204
|
+
SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
107
205
|
end
|
108
206
|
|
109
|
-
def
|
110
|
-
|
111
|
-
|
207
|
+
def kill_all_runners
|
208
|
+
RUNNERS.each do |thread, worker|
|
209
|
+
GRAVEYARD[thread] = RUNNERS.delete(thread)
|
210
|
+
end
|
112
211
|
end
|
113
212
|
|
114
|
-
|
213
|
+
def maintain_runner_count
|
214
|
+
current_runner_count = RUNNERS.size - runner_count
|
115
215
|
|
116
|
-
|
117
|
-
|
118
|
-
|
216
|
+
spawn_missing_runners if current_runner_count < 0
|
217
|
+
murder_extra_runners if current_runner_count > 0
|
218
|
+
reap_all_runners
|
219
|
+
reap_graveyard
|
119
220
|
end
|
120
221
|
|
121
|
-
def
|
122
|
-
|
123
|
-
|
124
|
-
|
222
|
+
def murder_extra_runners
|
223
|
+
until RUNNERS.size == runner_count
|
224
|
+
thread, worker = RUNNERS.shift
|
225
|
+
if worker.working?
|
226
|
+
logger.debug "creeper [murder] => soft quit" if $DEBUG
|
227
|
+
worker.soft_quit = true
|
228
|
+
GRAVEYARD[thread] = worker
|
229
|
+
else
|
230
|
+
logger.debug "creeper [murder] => hard quit" if $DEBUG
|
231
|
+
worker.session.disconnect
|
232
|
+
thread.kill
|
233
|
+
thread.join
|
234
|
+
end
|
235
|
+
end
|
125
236
|
end
|
126
237
|
|
127
|
-
def
|
128
|
-
|
238
|
+
def reap_all_runners
|
239
|
+
RUNNERS.each do |thread, worker|
|
240
|
+
GRAVEYARD[thread] = worker if not thread.alive?
|
241
|
+
end
|
242
|
+
end
|
129
243
|
|
130
|
-
|
131
|
-
|
132
|
-
|
244
|
+
def reap_graveyard(graceful = true)
|
245
|
+
GRAVEYARD.each do |thread, worker|
|
246
|
+
if graceful and worker.working?
|
247
|
+
logger.debug "creeper [graveyard] => soft quit" if $DEBUG
|
248
|
+
worker.soft_quit = true
|
249
|
+
else
|
250
|
+
logger.debug "creeper [graveyard] => hard quit" if $DEBUG
|
251
|
+
thread.kill rescue nil
|
252
|
+
thread.join
|
253
|
+
GRAVEYARD.delete(thread)
|
254
|
+
end
|
133
255
|
end
|
256
|
+
end
|
134
257
|
|
135
|
-
|
258
|
+
def spawn_missing_runners
|
259
|
+
until RUNNERS.size == runner_count
|
260
|
+
worker = Creeper::Worker.new(jobs: jobs)
|
261
|
+
thread = worker.start
|
262
|
+
RUNNERS[thread] = worker
|
263
|
+
end
|
136
264
|
end
|
137
265
|
|
138
|
-
def
|
139
|
-
|
266
|
+
def reset_proc_name
|
267
|
+
proc_name "creeper(#{$$}) [#{runner_count}]"
|
140
268
|
end
|
141
269
|
|
142
|
-
def
|
143
|
-
|
270
|
+
def proc_name(tag)
|
271
|
+
$0 = ([
|
272
|
+
File.basename(Creeper::START_CTX[0]),
|
273
|
+
tag
|
274
|
+
]).concat(Creeper::START_CTX[:argv]).join(' ')
|
144
275
|
end
|
145
276
|
|
146
|
-
|
147
|
-
|
277
|
+
##
|
278
|
+
|
279
|
+
public
|
280
|
+
|
281
|
+
def clear!
|
282
|
+
@default_session.disconnect if @default_session
|
283
|
+
@default_session = nil
|
148
284
|
end
|
149
285
|
|
150
|
-
def
|
151
|
-
|
286
|
+
def default_session
|
287
|
+
@default_session ||= Creeper::Session.new
|
152
288
|
end
|
153
289
|
|
154
|
-
def
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
290
|
+
def enqueue(job, args = {}, opts = {})
|
291
|
+
default_session.enqueue(job, args, opts)
|
292
|
+
end
|
293
|
+
|
294
|
+
def job(name, &block)
|
295
|
+
default_session.job(name, &block)
|
296
|
+
end
|
297
|
+
|
298
|
+
def before(&block)
|
299
|
+
default_session.before(&block)
|
160
300
|
end
|
301
|
+
|
302
|
+
def error(&block)
|
303
|
+
default_session.error(&block)
|
304
|
+
end
|
305
|
+
|
161
306
|
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module Creeper
|
2
|
+
class Session
|
3
|
+
|
4
|
+
attr_accessor :beanstalk, :beanstalk_url
|
5
|
+
attr_accessor :before_handlers, :error_handler, :handlers
|
6
|
+
|
7
|
+
def initialize(parent_session = nil)
|
8
|
+
if parent_session
|
9
|
+
@beanstalk_url = parent_session.beanstalk_url
|
10
|
+
@before_handlers = parent_session.before_handlers
|
11
|
+
@error_handler = parent_session.error_handler
|
12
|
+
@handlers = parent_session.handlers
|
13
|
+
end
|
14
|
+
|
15
|
+
@before_handlers ||= []
|
16
|
+
@handlers ||= {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def clear!
|
20
|
+
handlers.clear
|
21
|
+
before_handlers.clear
|
22
|
+
self.error_handler = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
## class methods ##
|
26
|
+
|
27
|
+
def self.beanstalk_addresses(beanstalk_url)
|
28
|
+
uris = beanstalk_url.split(/[\s,]+/)
|
29
|
+
uris.map do |uri|
|
30
|
+
beanstalk_host_and_port(uri)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.beanstalk_host_and_port(uri_string)
|
35
|
+
uri = URI.parse(uri_string)
|
36
|
+
raise(BadURL, uri_string) if uri.scheme != 'beanstalk'
|
37
|
+
"#{uri.host}:#{uri.port || 11300}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.beanstalk_url
|
41
|
+
ENV['BEANSTALK_URL'] || 'beanstalk://localhost/'
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
|
46
|
+
## beanstalk ##
|
47
|
+
|
48
|
+
class BadURL < RuntimeError; end
|
49
|
+
|
50
|
+
def connect
|
51
|
+
Beanstalk::Pool.new(beanstalk_addresses)
|
52
|
+
end
|
53
|
+
|
54
|
+
def disconnect
|
55
|
+
beanstalk.close.tap do
|
56
|
+
@beanstalk = nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def reconnect
|
61
|
+
disconnect rescue nil
|
62
|
+
beanstalk
|
63
|
+
end
|
64
|
+
|
65
|
+
def beanstalk
|
66
|
+
@beanstalk ||= connect
|
67
|
+
end
|
68
|
+
|
69
|
+
def beanstalk_url
|
70
|
+
@beanstalk_url ||= singleton_class.beanstalk_url
|
71
|
+
end
|
72
|
+
|
73
|
+
def beanstalk_addresses
|
74
|
+
singleton_class.beanstalk_addresses(beanstalk_url)
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
|
79
|
+
## handlers ##
|
80
|
+
|
81
|
+
def job(name, &block)
|
82
|
+
handlers[name] = block
|
83
|
+
end
|
84
|
+
|
85
|
+
def before(&block)
|
86
|
+
before_handlers << block
|
87
|
+
end
|
88
|
+
|
89
|
+
def error(&block)
|
90
|
+
self.error_handler = block
|
91
|
+
end
|
92
|
+
|
93
|
+
def all_jobs
|
94
|
+
handlers.keys
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
|
99
|
+
## queue ##
|
100
|
+
|
101
|
+
def enqueue(job, args = {}, opts = {})
|
102
|
+
pri = opts[:pri] || 65536
|
103
|
+
delay = [0, opts[:delay].to_i].max
|
104
|
+
ttr = opts[:ttr] || 120
|
105
|
+
beanstalk.use job
|
106
|
+
beanstalk.put [ job, args ].to_json, pri, delay, ttr
|
107
|
+
rescue Beanstalk::NotConnected => e
|
108
|
+
failed_connection(e)
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
|
113
|
+
protected
|
114
|
+
|
115
|
+
def failed_connection(e)
|
116
|
+
log_error exception_message(e)
|
117
|
+
log_error "*** Failed connection to #{beanstalk_url}"
|
118
|
+
log_error "*** Check that beanstalkd is running (or set a different BEANSTALK_URL)"
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
def log(msg)
|
123
|
+
Creeper.logger.info(msg)
|
124
|
+
end
|
125
|
+
|
126
|
+
def log_error(msg)
|
127
|
+
Creeper.error_logger.error(msg)
|
128
|
+
end
|
129
|
+
|
130
|
+
def exception_message(e)
|
131
|
+
msg = [ "Exception #{e.class} -> #{e.message}" ]
|
132
|
+
|
133
|
+
base = File.expand_path(Dir.pwd) + '/'
|
134
|
+
e.backtrace.each do |t|
|
135
|
+
msg << " #{File.expand_path(t).gsub(/#{base}/, '')}"
|
136
|
+
end
|
137
|
+
|
138
|
+
msg.join("\n")
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
data/lib/creeper/version.rb
CHANGED
data/lib/creeper/worker.rb
CHANGED
@@ -1,168 +1,173 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'uri'
|
4
|
-
require 'timeout'
|
5
|
-
|
6
|
-
class Creeper::Worker
|
7
|
-
def initialize
|
8
|
-
@handlers = Creeper.job_handlers
|
9
|
-
@before_handlers = Creeper.before_handlers
|
10
|
-
@error_handler = Creeper.error_handler
|
11
|
-
end
|
1
|
+
module Creeper
|
2
|
+
class Worker
|
12
3
|
|
13
|
-
|
14
|
-
class NoSuchJob < RuntimeError; end
|
15
|
-
class JobTimeout < RuntimeError; end
|
16
|
-
class BadURL < RuntimeError; end
|
4
|
+
## utilities ##
|
17
5
|
|
18
|
-
|
19
|
-
|
20
|
-
|
6
|
+
class NoJobsDefined < RuntimeError; end
|
7
|
+
class NoSuchJob < RuntimeError; end
|
8
|
+
class JobTimeout < RuntimeError; end
|
9
|
+
class BadURL < RuntimeError; end
|
21
10
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
11
|
+
def logger
|
12
|
+
Creeper.logger
|
13
|
+
end
|
26
14
|
|
27
|
-
|
28
|
-
|
29
|
-
|
15
|
+
def log_exception(*args)
|
16
|
+
Creeper.log_exception(*args)
|
17
|
+
end
|
30
18
|
|
31
|
-
|
32
|
-
|
33
|
-
|
19
|
+
def error_logger
|
20
|
+
Creeper.error_logger
|
21
|
+
end
|
34
22
|
|
35
|
-
|
23
|
+
def log(msg)
|
24
|
+
logger.info(msg)
|
25
|
+
end
|
36
26
|
|
37
|
-
|
38
|
-
|
27
|
+
def log_error(msg)
|
28
|
+
error_logger.error(msg)
|
39
29
|
end
|
40
30
|
|
41
|
-
|
31
|
+
##
|
42
32
|
|
43
|
-
|
33
|
+
attr_reader :options, :session, :thread
|
34
|
+
attr_accessor :soft_quit, :working
|
44
35
|
|
45
|
-
|
46
|
-
|
36
|
+
def initialize(options = {})#jobs = nil, parent_session = , session = nil)
|
37
|
+
@options = options
|
47
38
|
end
|
48
|
-
rescue Beanstalk::NotConnected => e
|
49
|
-
failed_connection(e)
|
50
|
-
end
|
51
39
|
|
52
|
-
|
53
|
-
prep(jobs)
|
54
|
-
loop { work_one_job }
|
55
|
-
end
|
40
|
+
### config ###
|
56
41
|
|
57
|
-
|
58
|
-
|
59
|
-
job = beanstalk.reserve
|
60
|
-
name, args = JSON.parse job.body
|
61
|
-
log_job_begin(name, args)
|
62
|
-
handler = @handlers[name]
|
63
|
-
raise(NoSuchJob, name) unless handler
|
64
|
-
|
65
|
-
begin
|
66
|
-
if defined? @before_handlers and @before_handlers.respond_to? :each
|
67
|
-
@before_handlers.each do |block|
|
68
|
-
block.call(name)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
handler.call(args)
|
72
|
-
end
|
73
|
-
|
74
|
-
job.delete
|
75
|
-
log_job_end(name)
|
76
|
-
rescue Beanstalk::NotConnected => e
|
77
|
-
failed_connection(e)
|
78
|
-
rescue SystemExit
|
79
|
-
raise
|
80
|
-
rescue => e
|
81
|
-
log_error exception_message(e)
|
82
|
-
job.bury rescue nil
|
83
|
-
log_job_end(name, 'failed') if @job_begun
|
84
|
-
if error_handler
|
85
|
-
if error_handler.arity == 1
|
86
|
-
error_handler.call(e)
|
87
|
-
else
|
88
|
-
error_handler.call(e, name, args)
|
89
|
-
end
|
42
|
+
def session
|
43
|
+
@session ||= Creeper::Session.new(options[:parent_session] || Creeper.default_session)
|
90
44
|
end
|
91
|
-
end
|
92
45
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
exit 1
|
98
|
-
end
|
46
|
+
def soft_quit
|
47
|
+
@soft_quit ||= false
|
48
|
+
end
|
49
|
+
alias :soft_quit? :soft_quit
|
99
50
|
|
100
|
-
|
101
|
-
|
102
|
-
args_flat = unless args.empty?
|
103
|
-
'(' + args.inject([]) do |accum, (key,value)|
|
104
|
-
accum << "#{key}=#{value}"
|
105
|
-
end.join(' ') + ')'
|
106
|
-
else
|
107
|
-
''
|
51
|
+
def working
|
52
|
+
@working ||= false
|
108
53
|
end
|
54
|
+
alias :working? :working
|
109
55
|
|
110
|
-
|
111
|
-
@job_begun = Time.now
|
112
|
-
end
|
56
|
+
###
|
113
57
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
ms = (ellapsed.to_f * 1000).to_i
|
118
|
-
log "Finished #{name} in #{ms}ms #{failed ? ' (failed)' : ''}"
|
119
|
-
end
|
58
|
+
def beanstalk
|
59
|
+
session.beanstalk
|
60
|
+
end
|
120
61
|
|
121
|
-
|
122
|
-
|
123
|
-
|
62
|
+
def clear!
|
63
|
+
@soft_quit = false
|
64
|
+
@working = false
|
65
|
+
@session = nil
|
66
|
+
end
|
124
67
|
|
125
|
-
|
126
|
-
|
127
|
-
end
|
68
|
+
def prepare
|
69
|
+
raise NoJobsDefined if session.handlers.empty?
|
128
70
|
|
129
|
-
|
130
|
-
@beanstalk ||= Beanstalk::Pool.new(beanstalk_addresses)
|
131
|
-
end
|
71
|
+
jobs = options[:jobs] || session.all_jobs
|
132
72
|
|
133
|
-
|
134
|
-
|
135
|
-
|
73
|
+
jobs.each do |job|
|
74
|
+
raise(NoSuchJob, job) unless session.handlers.has_key?(job)
|
75
|
+
end
|
136
76
|
|
137
|
-
|
138
|
-
uris = beanstalk_url.split(/[\s,]+/)
|
139
|
-
uris.map {|uri| beanstalk_host_and_port(uri)}
|
140
|
-
end
|
77
|
+
logger.info "Working #{jobs.size} jobs: [ #{jobs.join(' ')} ]"
|
141
78
|
|
142
|
-
|
143
|
-
uri = URI.parse(uri_string)
|
144
|
-
raise(BadURL, uri_string) if uri.scheme != 'beanstalk'
|
145
|
-
"#{uri.host}:#{uri.port || 11300}"
|
146
|
-
end
|
79
|
+
jobs.each { |job| beanstalk.watch(job) }
|
147
80
|
|
148
|
-
|
149
|
-
|
81
|
+
beanstalk.list_tubes_watched.each do |server, tubes|
|
82
|
+
tubes.each { |tube| beanstalk.ignore(tube) unless jobs.include?(tube) }
|
83
|
+
end
|
84
|
+
rescue Beanstalk::NotConnected => e
|
85
|
+
failed_connection(e)
|
86
|
+
end
|
150
87
|
|
151
|
-
|
152
|
-
|
153
|
-
|
88
|
+
def work
|
89
|
+
prepare
|
90
|
+
loop { work_one_job }
|
154
91
|
end
|
155
92
|
|
156
|
-
|
157
|
-
|
93
|
+
def work_one_job
|
94
|
+
stop if soft_quit?
|
158
95
|
|
159
|
-
|
160
|
-
|
161
|
-
|
96
|
+
job = beanstalk.reserve
|
97
|
+
name, args = JSON.parse job.body
|
98
|
+
log_job_begin(name, args)
|
99
|
+
handler = session.handlers[name]
|
100
|
+
raise(NoSuchJob, name) unless handler
|
162
101
|
|
163
|
-
|
164
|
-
|
165
|
-
|
102
|
+
begin
|
103
|
+
session.before_handlers.each do |block|
|
104
|
+
block.call(name)
|
105
|
+
end
|
106
|
+
handler.call(args)
|
107
|
+
end
|
166
108
|
|
167
|
-
|
109
|
+
job.delete
|
110
|
+
log_job_end(name)
|
111
|
+
rescue Beanstalk::NotConnected => e
|
112
|
+
failed_connection(e)
|
113
|
+
rescue SystemExit
|
114
|
+
puts "FART"
|
115
|
+
raise
|
116
|
+
rescue => e
|
117
|
+
log_error exception_message(e)
|
118
|
+
job.bury rescue nil
|
119
|
+
log_job_end(name, 'failed') if @job_begun
|
120
|
+
if session.error_handler
|
121
|
+
if session.error_handler.arity == 1
|
122
|
+
session.error_handler.call(e)
|
123
|
+
else
|
124
|
+
session.error_handler.call(e, name, args)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
168
128
|
|
129
|
+
def start
|
130
|
+
@thread = Thread.new do
|
131
|
+
work
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def stop
|
136
|
+
logger.info "worker dying: #{Thread.current.inspect}"
|
137
|
+
session.disconnect
|
138
|
+
@thread.kill
|
139
|
+
end
|
140
|
+
|
141
|
+
protected
|
142
|
+
|
143
|
+
def failed_connection(e)
|
144
|
+
session.send(:failed_connection, e)
|
145
|
+
end
|
146
|
+
|
147
|
+
def exception_message(e)
|
148
|
+
session.send(:exception_message, e)
|
149
|
+
end
|
150
|
+
|
151
|
+
def log_job_begin(name, args)
|
152
|
+
@working = true
|
153
|
+
args_flat = unless args.empty?
|
154
|
+
'(' + args.inject([]) do |accum, (key,value)|
|
155
|
+
accum << "#{key}=#{value}"
|
156
|
+
end.join(' ') + ')'
|
157
|
+
else
|
158
|
+
''
|
159
|
+
end
|
160
|
+
|
161
|
+
log [ "Working", name, args_flat ].join(' ')
|
162
|
+
@job_begun = Time.now
|
163
|
+
end
|
164
|
+
|
165
|
+
def log_job_end(name, failed=false)
|
166
|
+
@working = false
|
167
|
+
ellapsed = Time.now - @job_begun
|
168
|
+
ms = (ellapsed.to_f * 1000).to_i
|
169
|
+
log "Finished #{name} in #{ms}ms #{failed ? ' (failed)' : ''}"
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Creeper::Session do
|
4
|
+
|
5
|
+
it "parses BEANSTALK_URL" do
|
6
|
+
ENV['BEANSTALK_URL'] = "beanstalk://localhost:12300"
|
7
|
+
Creeper::Session.new.beanstalk_addresses.should == ["localhost:12300"]
|
8
|
+
ENV['BEANSTALK_URL'] = "beanstalk://localhost:12300/, beanstalk://localhost:12301/"
|
9
|
+
Creeper::Session.new.beanstalk_addresses.should == ["localhost:12300","localhost:12301"]
|
10
|
+
ENV['BEANSTALK_URL'] = "beanstalk://localhost:12300 beanstalk://localhost:12301"
|
11
|
+
Creeper::Session.new.beanstalk_addresses.should == ["localhost:12300","localhost:12301"]
|
12
|
+
ENV['BEANSTALK_URL'] = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Creeper::Worker do
|
4
|
+
|
5
|
+
context 'a worker' do
|
6
|
+
|
7
|
+
subject { Creeper::Worker.new }
|
8
|
+
|
9
|
+
after(:each) do
|
10
|
+
subject.clear!
|
11
|
+
Creeper.clear!
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should have a session different from the Creeper.default_session' do
|
15
|
+
subject.session.should_not == Creeper.default_session
|
16
|
+
subject.session.beanstalk.should_not == Creeper.default_session.beanstalk
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -13,7 +13,7 @@ describe Creeper do
|
|
13
13
|
w = Creeper::Worker.new
|
14
14
|
w.stub(:exception_message)
|
15
15
|
w.stub(:log)
|
16
|
-
w.
|
16
|
+
w.prepare
|
17
17
|
w.work_one_job
|
18
18
|
val.should == $result
|
19
19
|
end
|
@@ -25,7 +25,7 @@ describe Creeper do
|
|
25
25
|
w = Creeper::Worker.new
|
26
26
|
w.stub(:exception_message)
|
27
27
|
w.stub(:log)
|
28
|
-
w.
|
28
|
+
w.prepare
|
29
29
|
w.work_one_job
|
30
30
|
$handled.should_not == nil
|
31
31
|
'my.job'.should == $job_name
|
@@ -40,7 +40,7 @@ describe Creeper do
|
|
40
40
|
w = Creeper::Worker.new
|
41
41
|
w.stub(:exception_message)
|
42
42
|
w.stub(:log)
|
43
|
-
w.
|
43
|
+
w.prepare
|
44
44
|
w.work_one_job
|
45
45
|
exception.should == $handled
|
46
46
|
end
|
@@ -52,7 +52,7 @@ describe Creeper do
|
|
52
52
|
w = Creeper::Worker.new
|
53
53
|
w.stub(:exception_message)
|
54
54
|
w.stub(:log)
|
55
|
-
w.
|
55
|
+
w.prepare
|
56
56
|
w.work_one_job
|
57
57
|
false.should == $handled
|
58
58
|
end
|
@@ -64,7 +64,7 @@ describe Creeper do
|
|
64
64
|
w = Creeper::Worker.new
|
65
65
|
w.stub(:exception_message)
|
66
66
|
w.stub(:log)
|
67
|
-
w.
|
67
|
+
w.prepare
|
68
68
|
w.work_one_job
|
69
69
|
$handled.should == "didn't time out"
|
70
70
|
end
|
@@ -76,7 +76,7 @@ describe Creeper do
|
|
76
76
|
w = Creeper::Worker.new
|
77
77
|
w.stub(:exception_message)
|
78
78
|
w.stub(:log)
|
79
|
-
w.
|
79
|
+
w.prepare
|
80
80
|
w.work_one_job
|
81
81
|
true.should == $handled
|
82
82
|
end
|
@@ -88,7 +88,7 @@ describe Creeper do
|
|
88
88
|
w = Creeper::Worker.new
|
89
89
|
w.stub(:exception_message)
|
90
90
|
w.stub(:log)
|
91
|
-
w.
|
91
|
+
w.prepare
|
92
92
|
w.work_one_job
|
93
93
|
'my.job'.should == $jobname
|
94
94
|
end
|
@@ -100,7 +100,7 @@ describe Creeper do
|
|
100
100
|
w = Creeper::Worker.new
|
101
101
|
w.stub(:exception_message)
|
102
102
|
w.stub(:log)
|
103
|
-
w.
|
103
|
+
w.prepare
|
104
104
|
w.work_one_job
|
105
105
|
true.should == $handled
|
106
106
|
end
|
@@ -113,21 +113,12 @@ describe Creeper do
|
|
113
113
|
w = Creeper::Worker.new
|
114
114
|
w.stub(:exception_message)
|
115
115
|
w.stub(:log)
|
116
|
-
w.
|
116
|
+
w.prepare
|
117
117
|
w.work_one_job
|
118
118
|
$handled.should_not == nil
|
119
119
|
'my.job'.should == $job_name
|
120
120
|
{'foo' => 123}.should == $job_args
|
121
121
|
end
|
122
|
-
|
123
|
-
it "parse BEANSTALK_URL" do
|
124
|
-
ENV['BEANSTALK_URL'] = "beanstalk://localhost:12300"
|
125
|
-
Creeper.beanstalk_addresses.should == ["localhost:12300"]
|
126
|
-
ENV['BEANSTALK_URL'] = "beanstalk://localhost:12300/, beanstalk://localhost:12301/"
|
127
|
-
Creeper.beanstalk_addresses.should == ["localhost:12300","localhost:12301"]
|
128
|
-
ENV['BEANSTALK_URL'] = "beanstalk://localhost:12300 beanstalk://localhost:12301"
|
129
|
-
Creeper.beanstalk_addresses.should == ["localhost:12300","localhost:12301"]
|
130
|
-
end
|
131
122
|
|
132
123
|
def with_an_error_handler
|
133
124
|
Creeper.error do |e, job_name, args|
|
@@ -136,6 +127,5 @@ describe Creeper do
|
|
136
127
|
$job_args = args
|
137
128
|
end
|
138
129
|
end
|
139
|
-
|
140
130
|
|
141
131
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: creeper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,17 +9,17 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-05-
|
12
|
+
date: 2012-05-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: pry
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
20
20
|
- !ruby/object:Gem::Version
|
21
21
|
version: '0'
|
22
|
-
type: :
|
22
|
+
type: :development
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
@@ -44,7 +44,23 @@ dependencies:
|
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '0'
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
|
-
name:
|
47
|
+
name: beanstalk-client
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: kgio
|
48
64
|
requirement: !ruby/object:Gem::Requirement
|
49
65
|
none: false
|
50
66
|
requirements:
|
@@ -74,11 +90,13 @@ files:
|
|
74
90
|
- Rakefile
|
75
91
|
- creeper.gemspec
|
76
92
|
- lib/creeper.rb
|
93
|
+
- lib/creeper/session.rb
|
77
94
|
- lib/creeper/version.rb
|
78
95
|
- lib/creeper/worker.rb
|
79
|
-
- spec/
|
96
|
+
- spec/lib/creeper/session_spec.rb
|
97
|
+
- spec/lib/creeper/worker_spec.rb
|
98
|
+
- spec/lib/creeper_spec.rb
|
80
99
|
- spec/spec_helper.rb
|
81
|
-
- spec/worker_spec.rb
|
82
100
|
homepage: https://github.com/lyondhill/creeper
|
83
101
|
licenses: []
|
84
102
|
post_install_message:
|
@@ -105,7 +123,8 @@ specification_version: 3
|
|
105
123
|
summary: A better solution for io bound jobs, same as stalker in functionality but
|
106
124
|
more threadie.
|
107
125
|
test_files:
|
108
|
-
- spec/
|
126
|
+
- spec/lib/creeper/session_spec.rb
|
127
|
+
- spec/lib/creeper/worker_spec.rb
|
128
|
+
- spec/lib/creeper_spec.rb
|
109
129
|
- spec/spec_helper.rb
|
110
|
-
- spec/worker_spec.rb
|
111
130
|
has_rdoc:
|
data/spec/worker_spec.rb
DELETED