creeper 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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.add_dependency "beanstalk-client"
18
+ gem.add_development_dependency 'pry'
19
19
  gem.add_development_dependency 'rspec'
20
- gem.add_dependency 'pry'
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
- require "creeper/version"
3
- require "creeper/worker"
2
+
4
3
  require 'json'
5
4
  require 'uri'
6
5
  require 'timeout'
7
6
 
8
- STDOUT.sync = true
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
- def connect(url)
14
- @@url = url
15
- beanstalk
54
+ ## utilities ##
55
+
56
+ def logger
57
+ @logger ||= Logger.new($stdout)
16
58
  end
17
59
 
18
- def enqueue(job, args={}, opts={})
19
- pri = opts[:pri] || 65536
20
- delay = [0, opts[:delay].to_i].max
21
- ttr = opts[:ttr] || 120
22
- beanstalk.use job
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 job(j, &block)
29
- @@handlers ||= {}
30
- @@handlers[j] = block
67
+ def error_logger
68
+ @error_logger ||= Logger.new($stderr)
31
69
  end
32
70
 
33
- def before(&block)
34
- @@before_handlers ||= []
35
- @@before_handlers << block
71
+ ##
72
+
73
+ ## main process ##
74
+
75
+ ### config ###
76
+
77
+ def patience
78
+ @patience ||= 60
36
79
  end
37
80
 
38
- def error(&blk)
39
- @@error_handler = blk
81
+ def runner_count
82
+ @runner_count ||= 1
40
83
  end
41
84
 
42
- def running
43
- @@running ||= []
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
- @@soft_quit ||= false
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
- @@soft_quit = soft_quit
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 work(jobs=nil, thread_count=1)
55
- thread_count.times do
56
- w = Creeper::Worker.new()
57
- t = Thread.new do
58
- w.work(jobs)
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
- while not soft_quit?
64
- running.each_with_index do |runner, index|
65
- if not runner[:thread].alive?
66
- w = Creeper::Worker.new()
67
- t = Thread.new do
68
- w.work(jobs)
69
- end
70
- running[index] << {thread: t, worker: w}
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
- sleep 1
74
- end
75
- running.each do |runner|
76
- if runner[:worker].job_in_progress?
77
- log "Murder [scheduling]"
78
- runner[:worker].soft_quit = true
79
- else
80
- log "Murder [now]"
81
- runner[:thread].kill
82
- end
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
- running.each do |runner|
85
- runner[:thread].join
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
- log "SEPPUKU!!"
185
+ reap_graveyard(false)
186
+ logger.debug graceful ? "creeper gracefully stopped" : "creeper hard stopped" if $DEBUG
88
187
  end
89
188
 
90
- def failed_connection(e)
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
- def log(msg)
98
- puts msg
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 log_error(msg)
102
- STDERR.puts msg
197
+ def awaken_creeper
198
+ SELF_PIPE[1].kgio_trywrite('.') # wakeup creeper process from select
103
199
  end
104
200
 
105
- def beanstalk
106
- @@beanstalk ||= Beanstalk::Pool.new(beanstalk_addresses)
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 beanstalk_url
110
- return @@url if defined?(@@url) and @@url
111
- ENV['BEANSTALK_URL'] || 'beanstalk://localhost/'
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
- class BadURL < RuntimeError; end
213
+ def maintain_runner_count
214
+ current_runner_count = RUNNERS.size - runner_count
115
215
 
116
- def beanstalk_addresses
117
- uris = beanstalk_url.split(/[\s,]+/)
118
- uris.map {|uri| beanstalk_host_and_port(uri)}
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 beanstalk_host_and_port(uri_string)
122
- uri = URI.parse(uri_string)
123
- raise(BadURL, uri_string) if uri.scheme != 'beanstalk'
124
- "#{uri.host}:#{uri.port || 11300}"
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 exception_message(e)
128
- msg = [ "Exception #{e.class} -> #{e.message}" ]
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
- base = File.expand_path(Dir.pwd) + '/'
131
- e.backtrace.each do |t|
132
- msg << " #{File.expand_path(t).gsub(/#{base}/, '')}"
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
- msg.join("\n")
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 all_jobs
139
- @@handlers.keys
266
+ def reset_proc_name
267
+ proc_name "creeper(#{$$}) [#{runner_count}]"
140
268
  end
141
269
 
142
- def job_handlers
143
- @@handlers ||= {}
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
- def before_handlers
147
- @@before_handlers ||= []
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 error_handler
151
- @@error_handler ||= nil
286
+ def default_session
287
+ @default_session ||= Creeper::Session.new
152
288
  end
153
289
 
154
- def clear!
155
- @@soft_quit = false
156
- @@running = []
157
- @@handlers = nil
158
- @@before_handlers = nil
159
- @@error_handler = nil
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
@@ -1,3 +1,3 @@
1
1
  module Creeper
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -1,168 +1,173 @@
1
- require 'beanstalk-client'
2
- require 'json'
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
- class NoJobsDefined < RuntimeError; end
14
- class NoSuchJob < RuntimeError; end
15
- class JobTimeout < RuntimeError; end
16
- class BadURL < RuntimeError; end
4
+ ## utilities ##
17
5
 
18
- def soft_quit?
19
- @soft_quit ||= false
20
- end
6
+ class NoJobsDefined < RuntimeError; end
7
+ class NoSuchJob < RuntimeError; end
8
+ class JobTimeout < RuntimeError; end
9
+ class BadURL < RuntimeError; end
21
10
 
22
- def soft_quit=(soft_quit)
23
- # exit if !job_in_progress? && soft_quit
24
- @soft_quit = soft_quit
25
- end
11
+ def logger
12
+ Creeper.logger
13
+ end
26
14
 
27
- def job_in_progress?
28
- @in_progress ||= false
29
- end
15
+ def log_exception(*args)
16
+ Creeper.log_exception(*args)
17
+ end
30
18
 
31
- def prep(jobs=nil)
32
- raise NoJobsDefined unless defined?(@handlers)
33
- @error_handler = nil unless defined?(@error_handler)
19
+ def error_logger
20
+ Creeper.error_logger
21
+ end
34
22
 
35
- jobs ||= all_jobs
23
+ def log(msg)
24
+ logger.info(msg)
25
+ end
36
26
 
37
- jobs.each do |job|
38
- raise(NoSuchJob, job) unless @handlers[job]
27
+ def log_error(msg)
28
+ error_logger.error(msg)
39
29
  end
40
30
 
41
- log "Working #{jobs.size} jobs: [ #{jobs.join(' ')} ]"
31
+ ##
42
32
 
43
- jobs.each { |job| beanstalk.watch(job) }
33
+ attr_reader :options, :session, :thread
34
+ attr_accessor :soft_quit, :working
44
35
 
45
- beanstalk.list_tubes_watched.each do |server, tubes|
46
- tubes.each { |tube| beanstalk.ignore(tube) unless jobs.include?(tube) }
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
- def work(jobs=nil)
53
- prep(jobs)
54
- loop { work_one_job }
55
- end
40
+ ### config ###
56
41
 
57
- def work_one_job
58
- Thread.current.kill if soft_quit?
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
- def failed_connection(e)
94
- log_error exception_message(e)
95
- log_error "*** Failed connection to #{beanstalk_url}"
96
- log_error "*** Check that beanstalkd is running (or set a different BEANSTALK_URL)"
97
- exit 1
98
- end
46
+ def soft_quit
47
+ @soft_quit ||= false
48
+ end
49
+ alias :soft_quit? :soft_quit
99
50
 
100
- def log_job_begin(name, args)
101
- @in_progress = true
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
- log [ "Working", name, args_flat ].join(' ')
111
- @job_begun = Time.now
112
- end
56
+ ###
113
57
 
114
- def log_job_end(name, failed=false)
115
- @in_progress = false
116
- ellapsed = Time.now - @job_begun
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
- def log(msg)
122
- puts msg
123
- end
62
+ def clear!
63
+ @soft_quit = false
64
+ @working = false
65
+ @session = nil
66
+ end
124
67
 
125
- def log_error(msg)
126
- STDERR.puts msg
127
- end
68
+ def prepare
69
+ raise NoJobsDefined if session.handlers.empty?
128
70
 
129
- def beanstalk
130
- @beanstalk ||= Beanstalk::Pool.new(beanstalk_addresses)
131
- end
71
+ jobs = options[:jobs] || session.all_jobs
132
72
 
133
- def beanstalk_url
134
- Creeper.beanstalk_url
135
- end
73
+ jobs.each do |job|
74
+ raise(NoSuchJob, job) unless session.handlers.has_key?(job)
75
+ end
136
76
 
137
- def beanstalk_addresses
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
- def beanstalk_host_and_port(uri_string)
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
- def exception_message(e)
149
- msg = [ "Exception #{e.class} -> #{e.message}" ]
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
- base = File.expand_path(Dir.pwd) + '/'
152
- e.backtrace.each do |t|
153
- msg << " #{File.expand_path(t).gsub(/#{base}/, '')}"
88
+ def work
89
+ prepare
90
+ loop { work_one_job }
154
91
  end
155
92
 
156
- msg.join("\n")
157
- end
93
+ def work_one_job
94
+ stop if soft_quit?
158
95
 
159
- def all_jobs
160
- @handlers.keys
161
- end
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
- def error_handler
164
- @error_handler
165
- end
102
+ begin
103
+ session.before_handlers.each do |block|
104
+ block.call(name)
105
+ end
106
+ handler.call(args)
107
+ end
166
108
 
167
- end
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.prep
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.prep
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.prep
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.prep
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.prep
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.prep
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.prep
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.prep
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.prep
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
@@ -4,6 +4,7 @@ $:.push File.expand_path("../lib", __FILE__)
4
4
  require 'creeper'
5
5
  require 'pry'
6
6
 
7
+ Creeper.logger = Creeper.error_logger = Logger.new(nil)
7
8
 
8
9
  RSpec.configure do |config|
9
10
  end
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.2
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-09 00:00:00.000000000 Z
12
+ date: 2012-05-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: beanstalk-client
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: :runtime
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: pry
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/creeper_spec.rb
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/creeper_spec.rb
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
@@ -1,16 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Creeper::Worker do
4
- # before :each do
5
- # @base = Monitor::Base.new
6
- # end
7
-
8
- # after :each do
9
- # @base = nil
10
- # end
11
-
12
- it "shoud not create a vz object" do
13
- 2.should == 2
14
- end
15
-
16
- end