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