stalk_climber 0.0.6 → 0.1.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.
@@ -3,10 +3,17 @@ module StalkClimber
3
3
 
4
4
  class InvalidURIScheme < RuntimeError; end
5
5
 
6
- attr_reader :addresses, :test_tube
6
+ # Addresses the pool is connected to
7
+ attr_reader :addresses
7
8
 
8
- # Constructs a Beaneater::Pool from a less strict URL
9
- # +url+ can be a string i.e 'localhost:11300' or an array of addresses.
9
+ # Test tube used when probing Beanstalk server for information
10
+ attr_reader :test_tube
11
+
12
+ # Constructs a Beaneater::Pool from a less strict URL +url+ can be a string
13
+ # or an array of addresses. Valid URLs match any of the following forms:
14
+ # localhost (host only)
15
+ # 192.168.1.100:11300 (host and port)
16
+ # beanstalk://127.0.0.1:11300 (host and port prefixed by beanstalk scheme)
10
17
  def initialize(addresses = nil, test_tube = nil)
11
18
  @addresses = Array(parse_addresses(addresses) || host_from_env || Beaneater.configuration.beanstalkd_url)
12
19
  @test_tube = test_tube
@@ -14,16 +21,39 @@ module StalkClimber
14
21
  end
15
22
 
16
23
 
24
+ def tubes
25
+ @tubes ||= StalkClimber::Tubes.new(self)
26
+ end
27
+
28
+
17
29
  protected
18
30
 
19
- # Parses the given url into a collection of beanstalk addresses
31
+ # :call-seq:
32
+ # parse_addresses(addresses) => String
33
+ #
34
+ # Parses the given urls into a collection of beanstalk addresses
20
35
  def parse_addresses(addresses)
21
36
  return if addresses.empty?
22
37
  uris = addresses.is_a?(Array) ? addresses.dup : addresses.split(/[\s,]+/)
23
38
  uris.map! do |uri_string|
24
- uri = URI.parse(uri_string)
25
- raise(InvalidURIScheme, "Invalid beanstalk URI: #{uri_string}") unless uri.scheme == 'beanstalk'
26
- "#{uri.host}:#{uri.port || 11300}"
39
+ begin
40
+ uri = URI.parse(uri_string)
41
+ rescue URI::InvalidURIError
42
+ # IP based hosts without a scheme will fail to parse
43
+ end
44
+ if uri && uri.scheme && uri.host
45
+ raise(InvalidURIScheme, "Invalid beanstalk URI: #{uri_string}") unless uri.scheme == 'beanstalk'
46
+ host = uri.host
47
+ port = uri.port
48
+ else
49
+ # if parse failure or missing scheme or host, assume the uri format is a
50
+ # hostname optionally followed by a port.
51
+ # i.e. 'localhost:11300' or '0.0.0.0'
52
+ match = uri_string.split(/:/)
53
+ host = match[0]
54
+ port = match[1]
55
+ end
56
+ "#{host}:#{port || 11300}"
27
57
  end
28
58
  end
29
59
 
@@ -1,62 +1,83 @@
1
- require 'json'
2
-
3
1
  module StalkClimber
4
- class Job
2
+ class Job < Beaneater::Job
3
+
4
+ # Attributes that are retrieved each request by default
5
+ FRESH_ATTRIBUTES = %w[delay pri state time-left]
6
+
7
+ # Attributes that return cached values by default
8
+ CACHED_ATTRIBUTES = %w[age buries kicks releases reserves timeouts ttr tube]
9
+
10
+ # All attributes available via a job's stats
11
+ STATS_ATTRIBUTES = (CACHED_ATTRIBUTES + FRESH_ATTRIBUTES).sort!
5
12
 
6
- STATS_ATTRIBUTES = %w[age buries delay kicks pri releases reserves state time-left timeouts ttr tube]
7
- HASH_ATTRIBUTES = (STATS_ATTRIBUTES + %w[body connection id]).sort!.map!(&:to_sym)
8
- attr_reader :id
13
+ # Lookup table of which method to call to retrieve each stat
14
+ STATS_METHODS = Hash[
15
+ STATS_ATTRIBUTES.zip(STATS_ATTRIBUTES.map {|stat| stat.gsub(/-/, '_')})
16
+ ]
9
17
 
10
- STATS_ATTRIBUTES.each do |method_name|
11
- define_method method_name do |force_refresh = false|
12
- return stats(force_refresh)[method_name]
18
+ STATS_METHODS.each_pair do |stat, method_name|
19
+ force = FRESH_ATTRIBUTES.include?(stat)
20
+ define_method method_name do |force_refresh = force|
21
+ return stats(force_refresh).send(method_name)
13
22
  end
14
23
  end
15
24
 
16
25
 
17
- # Returns or fetches the body of the job obtained via the peek command
26
+ # :call-seq:
27
+ # body() => String
28
+ #
29
+ # Returns or fetches the body of the job. If not already present the body of
30
+ # the job is obtained via the peek command.
31
+ #
32
+ # job = StalkClimber::Job.new(connection.transmit('reserve'))
33
+ # job.body
34
+ # #=> "Work to be done"
18
35
  def body
19
36
  return @body ||= connection.transmit("peek #{id}")[:body]
20
37
  end
21
38
 
22
39
 
23
- # Returns the connection provided by the job data given to the initialize method
24
- def connection
25
- return @connection
26
- end
27
-
28
-
29
- # Deletes the job from beanstalk. If the job is not found it is assumed that it
30
- # has already been otherwise deleted.
40
+ # :call-seq:
41
+ # delete() => Hash{Symbol => String,Integer}
42
+ #
43
+ # Tries to delete the job from beanstalk. Returns the Beanstalkd response for the command.
44
+ # Raises Beaneater::NotFoundError if the job could not be found.
45
+ #
46
+ # job = StalkClimber::Job.new(connection.transmit('reserve'))
47
+ # job.delete
48
+ # #=> {:status=>"DELETED", :body=>nil, :connection=>#<Beaneater::Connection host="localhost" port=11300>}
31
49
  def delete
32
- return true if @status == 'DELETED'
33
- begin
34
- @connection.transmit("delete #{id}")
35
- rescue Beaneater::NotFoundError
36
- end
50
+ result = super
37
51
  @status = 'DELETED'
38
52
  @stats = nil
39
53
  @body = nil
40
- return true
54
+ return result
41
55
  end
42
56
 
43
57
 
58
+ # :call-seq:
59
+ # exists?() => Boolean
60
+ #
44
61
  # Determines if a job exists by retrieving stats for the job. If Beaneater can't find
45
62
  # the job then it does not exist and false is returned. The stats command is used
46
63
  # because it will return a response of a near constant size, whereas, depending on
47
64
  # the job, the peek command could return a much larger response. Rather than waste
48
65
  # the trip to the server, stats are updated each time the method is called.
66
+ #
67
+ # job = StalkClimber::Job.new(connection.transmit('reserve'))
68
+ # job.exists?
69
+ # #=> true
70
+ # job.delete
71
+ # job.exists?
72
+ # #=> false
49
73
  def exists?
50
- return false if @status == 'DELETED'
51
- begin
52
- stats(:force_refresh)
53
- return true
54
- rescue Beaneater::NotFoundError
55
- return false
56
- end
74
+ return @status == 'DELETED' ? false : super
57
75
  end
58
76
 
59
77
 
78
+ # :call-seq:
79
+ # new(job_data) => Job
80
+ #
60
81
  # Initializes a Job instance using +job_data+ which should be the Beaneater response to either
61
82
  # a put, peek, or stats-job command. Other Beaneater responses are not supported.
62
83
  #
@@ -67,124 +88,306 @@ module StalkClimber
67
88
  # Put provides only the ID of the job and as such yields the least informed instance. Both a
68
89
  # peek and stats-job call may be required to retrieve anything but the ID of the instance
69
90
  #
70
- # Peek provides the ID and body of the job. A stats-job call may be required to access anything
71
- # but the ID or body of the job.
91
+ # Peek and reserve provide the ID and body of the job. A stats-job call may be required to
92
+ # access anything but the ID or body of the job.
72
93
  #
73
94
  # Stats-job provides the most information about the job, but lacks the crtical component of the
74
95
  # job body. As such, a peek call would be required to access the body of the job.
96
+ #
97
+ # reserve_response = connection.transmit('reserve')
98
+ # StalkClimber::Job.new(reserve_response)
99
+ # #=> #<StalkClimber::Job id=1 body="Work to be done">
75
100
  def initialize(job_data)
76
101
  case job_data[:status]
77
102
  when 'INSERTED' # put
78
103
  @id = job_data[:id].to_i
79
104
  @body = @stats = nil
80
- when 'FOUND' # peek
105
+ when 'FOUND', 'RESERVED' # peek, reserve
81
106
  @id = job_data[:id].to_i
82
107
  @body = job_data[:body]
83
108
  @stats = nil
84
109
  when 'OK' # stats-job
85
- @stats = job_data[:body].dup
86
- @id = @stats.delete('id').to_i
110
+ @stats = Beaneater::StatStruct.from_hash(job_data[:body])
111
+ @id = @stats.id.to_i
87
112
  @body = nil
88
113
  else
89
114
  raise RuntimeError, "Unexpected job status: #{job_data[:status]}"
90
115
  end
91
116
  @status = job_data[:status]
92
117
  @connection = job_data[:connection]
118
+ # Don't set @reserved to force lookup each time to handle job TTR expiration
93
119
  end
94
120
 
95
121
 
122
+ # :call-seq:
123
+ # stats(force_refresh = true) => Beaneater::StatStruct
124
+ #
96
125
  # Returns or retrieves stats for the job. Optionally, a retrieve may be forced
97
126
  # by passing a non-false value for +force_refresh+
98
- def stats(force_refresh = false)
99
- return @stats unless @stats.nil? || force_refresh
100
- @stats = connection.transmit("stats-job #{id}")[:body]
101
- @stats.delete('id')
127
+ #
128
+ # job = StalkClimber::Job.new(peek_response)
129
+ # job.stats
130
+ # #=> #<Beaneater::StatStruct id=2523, tube="stalk_climber", state="ready", pri=0, age=25, delay=0, ttr=120, time_left=0, file=0, reserves=0, timeouts=0, releases=0, buries=0, kicks=0>
131
+ def stats(force_refresh = true)
132
+ if @stats.nil? || force_refresh
133
+ @stats = super()
134
+ end
102
135
  return @stats
103
136
  end
104
137
 
105
138
 
106
- # Returns a hash of all job attributes
139
+ # :call-seq:
140
+ # to_h() => Hash
141
+ #
142
+ # Returns a hash of all job attributes derived from updated stats
143
+ #
144
+ # job = StalkClimber::Job.new(peek_response)
145
+ # job.to_h
146
+ # #=> {"age"=>144, "body"=>"Work to be done", "buries"=>0, "connection"=>#<Beaneater::Connection host="localhost" port=11300>, "delay"=>0, "id"=>2523, "kicks"=>0, "pri"=>0, "releases"=>0, "reserves"=>0, "state"=>"ready", "time-left"=>0, "timeouts"=>0, "ttr"=>120, "tube"=>"stalk_climber"}
107
147
  def to_h
108
- return Hash[HASH_ATTRIBUTES.map { |attr| [attr, send(attr) ] } ]
148
+ stats
149
+ stats_pairs = STATS_METHODS.map { |stat, method_name| [stat, stats(false)[method_name]]}
150
+ stats_pairs.concat([['body', body], ['connection', connection], ['id', id]]).sort_by!(&:first)
151
+ return Hash[stats_pairs]
109
152
  end
110
153
 
111
154
 
155
+ # :call-seq:
156
+ # to_s() => String
157
+ #
158
+ # Returns string representation of job
159
+ #
160
+ # job = StalkClimber::Job.new(peek_response)
161
+ # job.to_s
162
+ # #=> #<StalkClimber::Job id=1 body="Work to be done">
163
+ def to_s
164
+ "#<StalkClimber::Job id=#{id} body=#{body.inspect}>"
165
+ end
166
+ alias :inspect :to_s
167
+
168
+
112
169
  # :method: age
170
+ # :call-seq:
171
+ # age(force_refresh = false) => Integer
172
+ #
113
173
  # Retrieves the age of the job from the job's stats. Passing a non-false value for
114
174
  # +force_refresh+ will force retrieval of updated stats for the job
115
- # :call-seq:
116
- # age(force_refresh = false)
175
+ #
176
+ # job = StalkClimber::Job.new(peek_response)
177
+ # job.age
178
+ # #=> 1024
117
179
 
118
180
  # :method: buries
181
+ # :call-seq:
182
+ # buries(force_refresh = false) => Integer
183
+ #
119
184
  # Retrieves the number of times the job has been buried from the job's stats. Passing
120
185
  # a non-false value for +force_refresh+ will force retrieval of updated stats for the job
186
+ #
187
+ # job = StalkClimber::Job.new(connection.transmit('peek-buried'))
188
+ # job.buries
189
+ # #=> 1
190
+
191
+ # :method: connection
192
+ # :call-seq:
193
+ # connection() => Beaneater::Connection
194
+ #
195
+ # Returns the Beaneater::Connection instance which retrieved job.
196
+ #
197
+ # job = StalkClimber::Job.new(peek_response)
198
+ # job.connection
199
+ # #=> #<Beaneater::Connection host="localhost" port=11300>
200
+
201
+ # :method: id
121
202
  # :call-seq:
122
- # buries(force_refresh = false)
203
+ # id() => Integer
204
+ #
205
+ # Returns the id of the job.
206
+ #
207
+ # job = StalkClimber::Job.new(peek_response)
208
+ # job.id
209
+ # #=> 1
210
+
211
+ # :method: bury
212
+ # :call-seq:
213
+ # bury() => Hash{Symbol => String,Integer}
214
+ #
215
+ # Sends command to bury a reserved job. Returns the Beanstalkd response for
216
+ # the command.
217
+ #
218
+ # job = StalkClimber::Job.new(connection.transmit('reserve'))
219
+ # job.bury
220
+ # #=> {:status=>"BURIED", :body=>nil, :connection=>#<Beaneater::Connection host="localhost" port=11300>}
123
221
 
124
222
  # :method: delay
223
+ # :call-seq:
224
+ # delay(force_refresh = false) => Integer
225
+ #
125
226
  # Retrieves the the remaining delay before the job is ready from the job's stats. Passing
126
- # a non-false value for +force_refresh+ will force retrieval of updated stats for the job
227
+ # a non-false value for +force_refresh+ will force retrieval of updated stats for the job.
228
+ # By default the value for delay is refreshed every time it is requested.
229
+ #
230
+ # job = StalkClimber::Job.new(connection.transmit('peek-delayed'))
231
+ # job.delay
232
+ # #=> 1024
233
+
234
+ # :method: kick
127
235
  # :call-seq:
128
- # delay(force_refresh = false)
236
+ # kick() => Hash{Symbol => String,Integer}
237
+ #
238
+ # Sends command to kick a buried job. Returns the Beanstalkd response for the command.
239
+ #
240
+ # job = StalkClimber::Job.new(connection.transmit('peek-buried'))
241
+ # job.kick
242
+ # #=> {:status=>"KICKED", :body=>nil, :connection=>#<Beaneater::Connection host="localhost" port=11300>}
129
243
 
130
244
  # :method: kicks
245
+ # :call-seq:
246
+ # kicks(force_refresh = false) => Integer
247
+ #
131
248
  # Retrieves the number of times the job has been kicked from the job's stats. Passing
132
249
  # a non-false value for +force_refresh+ will force retrieval of updated stats for the job
133
- # :call-seq:
134
- # kicks(force_refresh = false)
250
+ # By default the value for kicks is cached and will not be updated unless forced.
251
+ #
252
+ # job = StalkClimber::Job.new(connection.transmit('peek-buried'))
253
+ # job.kicks
254
+ # #=> 5
135
255
 
136
256
  # :method: pri
257
+ # :call-seq:
258
+ # pri(force_refresh = false) => Integer
259
+ #
137
260
  # Retrieves the priority of the job from the job's stats. Passing a non-false value for
138
- # +force_refresh+ will force retrieval of updated stats for the job
261
+ # +force_refresh+ will force retrieval of updated stats for the job.
262
+ # By default the the value for pri is refreshed every time it is requested.
263
+ #
264
+ # job = StalkClimber::Job.new(connection.transmit('peek-ready'))
265
+ # job.pri
266
+ # #=> 0
267
+
268
+ # :method: release
139
269
  # :call-seq:
140
- # pri(force_refresh = false)
270
+ # release(options = {}) => Hash{Symbol => String,Integer}
271
+ #
272
+ # Sends command to release a job back to ready state. Return value
273
+ # is the Beanstalkd response to the command.
274
+ #
275
+ # Options may include an Integer pri to set a new pri for the job and/or an
276
+ # Integer delay to assign a new delay to the job
277
+ #
278
+ # job = StalkClimber::Job.new(connection.transmit('reserve'))
279
+ # job.release(:delay => 120, :pri => 1024)
280
+ # #=> {:status=>"RELEASED", :body=>nil, :connection=>#<Beaneater::Connection host="localhost" port=11300>}
141
281
 
142
282
  # :method: releases
283
+ # :call-seq:
284
+ # releases(force_refresh = false) => Integer
285
+ #
143
286
  # Retrieves the number of times the job has been released from the job's stats. Passing
144
287
  # a non-false value for +force_refresh+ will force retrieval of updated stats for the job
288
+ # By default the value for releases is cached and will not be updated unless forced.
289
+ #
290
+ # job = StalkClimber::Job.new(connection.transmit('peek-ready'))
291
+ # job.releases
292
+ # #=> 3
293
+
294
+ # :method: reserved?
145
295
  # :call-seq:
146
- # releases(force_refresh = false)
296
+ # reserved?() => Boolean
297
+ #
298
+ # Returns true if a job is currently in a reserved state.
299
+ #
300
+ # job = StalkClimber::Job.new(connection.transmit('reserve'))
301
+ # job.reserved?
302
+ # #=> true
147
303
 
148
304
  # :method: reserves
305
+ # :call-seq:
306
+ # reserves(force_refresh = false) => Integer
307
+ #
149
308
  # Retrieves the number of times the job has been reserved from the job's stats. Passing
150
309
  # a non-false value for +force_refresh+ will force retrieval of updated stats for the job
151
- # :call-seq:
152
- # reserves(force_refresh = false)
310
+ # By default the value for reserves is cached and will not be updated unless forced.
311
+ #
312
+ # job = StalkClimber::Job.new(connection.transmit('reserve'))
313
+ # job.reserves
314
+ # #=> 1
153
315
 
154
316
  # :method: state
317
+ # :call-seq:
318
+ # state(force_refresh = false) => String
319
+ #
155
320
  # Retrieves the state of the job from the job's stats. Value will be one of "ready",
156
321
  # "delayed", "reserved", or "buried". Passing a non-false value for +force_refresh+
157
322
  # will force retrieval of updated stats for the job
158
- # :call-seq:
159
- # timeouts(force_refresh = false)
323
+ # By default the the value for state is refreshed every time it is requested.
324
+ #
325
+ # job = StalkClimber::Job.new(connection.transmit('peek-ready'))
326
+ # job.state
327
+ # #=> 'ready'
160
328
 
161
- # :method: time-left
329
+ # :method: time_left
330
+ # :call-seq:
331
+ # time_left(force_refresh = false) => Integer
332
+ #
162
333
  # Retrieves the number of seconds left until the server puts this job into the ready
163
334
  # queue. This number is only meaningful if the job is reserved or delayed. If the job
164
335
  # is reserved and this amount of time elapses before its state changes, it is considered
165
336
  # to have timed out. Passing a non-false value for +force_refresh+ will force retrieval
166
- # of updated stats for the job
167
- # :call-seq:
168
- # timeouts(force_refresh = false)
337
+ # of updated stats for the job By default the the value for time_left is refreshed every
338
+ # time it is requested.
339
+ #
340
+ # job = StalkClimber::Job.new(connection.transmit('reserve'))
341
+ # job.time_left
342
+ # #=> 119
169
343
 
170
344
  # :method: timeouts
345
+ # :call-seq:
346
+ # timeouts(force_refresh = false) => Integer
347
+ #
171
348
  # Retrieves the number of times the job has timed out from the job's stats. Passing
172
349
  # a non-false value for +force_refresh+ will force retrieval of updated stats for the job
350
+ # By default the value for timeouts is cached and will not be updated unless forced.
351
+ #
352
+ # job = StalkClimber::Job.new(connection.transmit('reserve'))
353
+ # job.timeouts
354
+ # #=> 0
355
+
356
+ # :method: touch
173
357
  # :call-seq:
174
- # timeouts(force_refresh = false)
358
+ # touch => Hash{Symbol => String,Integer}
359
+ #
360
+ # Sends command to touch job which extends the ttr. Returns the Beanstalkd response for
361
+ # the command.
362
+ #
363
+ # job = StalkClimber::Job.new(connection.transmit('reserve'))
364
+ # job.touch
365
+ # #=> {:status=>"TOUCHED", :body=>nil, :connection=>#<Beaneater::Connection host="localhost" port=11300>}
175
366
 
176
367
  # :method: ttr
368
+ # :call-seq:
369
+ # ttr(force_refresh = false) => Integer
370
+ #
177
371
  # Retrieves the time to run for the job, the number of seconds a worker is allowed to work
178
372
  # to run the job. Passing a non-false value for +force_refresh+ will force retrieval of
179
- # updated stats for the job
180
- # :call-seq:
181
- # timeouts(force_refresh = false)
373
+ # updated stats for the job By default the value for ttr is cached and will not be updated
374
+ # unless forced.
375
+ #
376
+ # job = StalkClimber::Job.new(connection.transmit('reserve'))
377
+ # job.ttr
378
+ # #=> 120
182
379
 
183
380
  # :method: tube
184
- # Retrieves the name of the tube that contains job. Passing a non-false value for
185
- # +force_refresh+ will force retrieval of updated stats for the job
186
381
  # :call-seq:
187
- # timeouts(force_refresh = false)
382
+ # tube => String
383
+ #
384
+ # Retrieves the name of the tube that contains job. Passing a non-false value for
385
+ # +force_refresh+ will force retrieval of updated stats for the job By default the
386
+ # value for tube is cached and will not be updated unless forced.
387
+ #
388
+ # job = StalkClimber::Job.new(connection.transmit('reserve'))
389
+ # job.tube
390
+ # #=> "stalk_climber"
188
391
 
189
392
  end
190
393
  end