timberline 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 29b010166f8c116bf4469cf6c09d3d65b0f769ee
4
- data.tar.gz: a06cc3487b8acaef76c85c87b7b56d32bd11785d
3
+ metadata.gz: 1e8abd9f7a068aed49df25b77c38489047e89ac1
4
+ data.tar.gz: ab2306c2f9ae72ee849e2fef5a4aa632b284b67a
5
5
  SHA512:
6
- metadata.gz: 6d5ced0644e73be8bd3bce8905e46d99ddeb36c61ae7e78890a8bbd9fa727fd8969301ee4e074e5d4518dda7aa77046238e7f068e48dc4da5968fc899398f682
7
- data.tar.gz: 75f4e3acc70d27d9021e8c0d4816f31ac40854a7a30f8bba1d625fb6a3498de8de12a3b89c99fe7102a4bea1a21c7903d4c69232557d87541a7a721f8174991f
6
+ metadata.gz: 5947f1e61eb13ccdf5663f1a678abc175683dbefcfd0e500d4844d14d287cd947a55120c807f5848a43ee7a9672457474ce123e64ed0923e5bf130f4185b0115
7
+ data.tar.gz: 4394761078279b91e7ccf86a2bd5c1b29eeb5755a05458b51d03339a4b6944d699071bc039c653f54fe6cf8f1a4beced5abfb89d8b1a1e5d9efc925531df649f
data/.yardoc/checksums ADDED
@@ -0,0 +1,8 @@
1
+ lib/timberline.rb bde38a760f2c89941eb1db0dd9bfe16824eacb90
2
+ lib/timberline/queue.rb f4463942e074f53cc10b14adaa9d00ce6a9fe606
3
+ lib/timberline/config.rb b53bd318344a956a8efa1058319c88411c982874
4
+ lib/timberline/worker.rb 3245a5b986391d28c1a0ea745fa09df5df375cf7
5
+ lib/timberline/version.rb 926b9f154a084b54b1d8636c711cbaa7d8166998
6
+ lib/timberline/envelope.rb fd34ad408e519b1655bf8163b4d81fb0600f7ca4
7
+ lib/timberline/exceptions.rb 3dcda3d2e61e920696eba97d2b84e1a1df642a7a
8
+ lib/timberline/anonymous_worker.rb 2f8fb80e35f3702ce61384d2b18ba334387046ef
Binary file
Binary file
Binary file
data/CHANGELOG CHANGED
@@ -1,3 +1,6 @@
1
+ 0.7.0
2
+ - Add Worker objects
3
+ - Add YARD documentation, publish docs to rubydoc.info
1
4
  0.6.0
2
5
  - Remove Rails-specific configuration logic in preparation for release of timberline-rails
3
6
  0.5.0
data/README.markdown CHANGED
@@ -1,6 +1,7 @@
1
1
  # Timberline
2
2
 
3
- ![](https://travis-ci.org/treehouse/timberline.svg)
3
+ ![](https://travis-ci.org/treehouse/timberline.svg) 
4
+ [![Gem Version](https://badge.fury.io/rb/timberline.svg)](http://badge.fury.io/rb/timberline)
4
5
 
5
6
  ## Purpose
6
7
 
@@ -21,8 +22,28 @@ kind of issues you might be dealing with:
21
22
  jobs as fast as you possibly can. To that end, Timberline uses blocking reads
22
23
  in Redis to pull jobs off of the queue as soon as they're available.
23
24
 
25
+ ## Documentation
26
+
27
+ Documentation for Timberline is available on rubydoc.info [here](http://rubydoc.info/github/treehouse/timberline/frames). The code itself is documented with YARD.
28
+
24
29
  ## Concepts
25
30
 
31
+ ### The Envelope
32
+
33
+ Sounds SOAPy, I know. The envelope is a simple object that wraps the data you
34
+ want to put on the queue - it's responsible for tracking things like the job ID,
35
+ the queue it was put on, how many times it's been retried, etc., etc. It's also
36
+ accessible to both the queue processor and whatever is putting jobs on the
37
+ queue, so if you want to be able to check in on the administrative details (or
38
+ add some of your own) this is a great place to do it instead of muddying up the
39
+ meat of your message.
40
+
41
+ ### Workers
42
+
43
+ Processing items on a Timberline queue is handled by Workers. A Worker can be
44
+ created as simply as providing a block that executes on job items, or you can
45
+ write your own Workers that perform special functionality.
46
+
26
47
  ### Retries
27
48
 
28
49
  Sometimes jobs just fail because of something that was outside of your control.
@@ -41,16 +62,6 @@ explicitly marked as bad jobs, or when they've been retried the maximum number
41
62
  of times. You can then check the jobs out and resubmit them to their original
42
63
  queue after you fix the issue.
43
64
 
44
- ### The Envelope
45
-
46
- Sounds SOAPy, I know. The envelope is a simple object that wraps the data you
47
- want to put on the queue - it's responsible for tracking things like the job ID,
48
- the queue it was put on, how many times it's been retried, etc., etc. It's also
49
- accessible to both the queue processor and whatever is putting jobs on the
50
- queue, so if you want to be able to check in on the administrative details (or
51
- add some of your own) this is a great place to do it instead of muddying up the
52
- meat of your message.
53
-
54
65
  ## Usage
55
66
 
56
67
  Timberline is designed to be as easy to work with as possible, and operates almost
@@ -170,11 +181,6 @@ Still to be done:
170
181
 
171
182
  - **Monitor** - A simple Sinatra interface for monitoring the statuses of queues and
172
183
  observing/resubmitting errored-out jobs.
173
- - **Documentation** - need to get Tomdoc added so that the API is more completely
174
- documented. For the time being, though, there are some fairly comprehensive
175
- test suites.
176
- - **Refactor** - the singleton model made sense at some point for Timberline but now it's
177
- cumbersome. Need to rewrite some of the basic stuff to be more OO-appropriate.
178
184
  - **Forking** - Timberline should support forking in its watcher model so that jobs can
179
185
  be run in parallel and independently of each other.
180
186
 
@@ -0,0 +1,48 @@
1
+ class Timberline
2
+ # The AnonymousWorker is exactly what it says on the tin - a way to process a queue
3
+ # without defining a new class, by instead just passing in a block that will be
4
+ # executed for each item on the queue as it's popped.
5
+ #
6
+ class AnonymousWorker < Worker
7
+
8
+ # Creates a new AnonymousWorker.
9
+ # The block's binding will be updated to give it access to retry_item
10
+ # and error_item so that the block can easily control the processing
11
+ # flow for queued items.
12
+ #
13
+ # @param [String] queue_name the name of the queue to watch
14
+ # @param [Block] block the block to run against each item that gets popped
15
+ # off the queue.
16
+ #
17
+ # @example Creating a simple AnonymousWorker
18
+ # AnonymousWorker.new "test_queue" { |item| puts item.contents }
19
+ #
20
+ # @return [AnonymousWorker]
21
+ #
22
+ def initialize(queue_name, &block)
23
+ super(queue_name)
24
+ @block = block
25
+ fix_block_binding
26
+ end
27
+
28
+ # @see Timberline::Worker#watch
29
+ #
30
+ def process_item(item)
31
+ @block.call(item, self)
32
+ end
33
+
34
+ private
35
+ def fix_block_binding
36
+ binding = @block.binding
37
+ binding.eval <<-HERE
38
+ def retry_item(item)
39
+ Worker.retry_item(item)
40
+ end
41
+
42
+ def error_item(item)
43
+ Worker.error_item(item)
44
+ end
45
+ HERE
46
+ end
47
+ end
48
+ end
@@ -1,8 +1,34 @@
1
1
  class Timberline
2
+ # Object that manages Timberline configuration. Responsible for Redis configs
3
+ # as well as Timberline-specific configuration values, like how many times an
4
+ # item should be retried in a queue.
5
+ #
6
+ # @attr [Integer] database part of the redis configuration - index of the
7
+ # redis database to use
8
+ # @attr [String] host part of the redis configuration - the hostname of the
9
+ # redis server
10
+ # @attr [Integer] port part of the redis configuration - the port of the
11
+ # redis server
12
+ # @attr [Integer] timeout part of the redis configuration - the timeout for
13
+ # the redis server
14
+ # @attr [String] password part of the redis configuration - the password for
15
+ # the redis server
16
+ # @attr [Logger] logger part of the redis configuration - the logger to use
17
+ # for the redis connection
18
+ # @attr [String] namespace the redis namespace for the keys that timberline
19
+ # will create and manage
20
+ # @attr [Integer] max_retries the number of times that an item on the queue
21
+ # should be allowed to retry itself before it is placed on the error queue
22
+ # @attr [Integer] stat_timeout the number of minutes that stats will stay live
23
+ # in redis before they are expired
24
+ #
2
25
  class Config
3
- attr_accessor :database, :host, :port, :timeout, :password, :logger, :namespace, :max_retries, :stat_timeout
26
+ attr_accessor :database, :host, :port, :timeout, :password,
27
+ :logger, :namespace, :max_retries, :stat_timeout
4
28
 
5
- def initialize
29
+ # Attemps to load configuration from TIMBERLINE_YAML, if it exists.
30
+ # Otherwise creates a default Config object.
31
+ def initialize
6
32
  if defined? TIMBERLINE_YAML
7
33
  if File.exists?(TIMBERLINE_YAML)
8
34
  yaml = YAML.load_file(TIMBERLINE_YAML)
@@ -13,18 +39,22 @@ class Timberline
13
39
  end
14
40
  end
15
41
 
42
+ # @return [String] the configured redis namespace, with a default of 'timberline'
16
43
  def namespace
17
44
  @namespace ||= 'timberline'
18
45
  end
19
46
 
47
+ # @return [Integer] the configured maximum number of retries, with a default of 5
20
48
  def max_retries
21
49
  @max_retries ||= 5
22
50
  end
23
51
 
52
+ # @return [Integer] the configured lifetime of stats (in minutes), with a default of 60
24
53
  def stat_timeout
25
54
  @stat_timeout ||= 60
26
55
  end
27
56
 
57
+ # @return [Hash] a Redis-ready hash for use in instantiating a new redis object.
28
58
  def redis_config
29
59
  config = {}
30
60
 
@@ -1,6 +1,22 @@
1
1
  class Timberline
2
+ # An Envelope in Timberline is what gets passed along on the queue.
3
+ # The message itself - the part that the workers should intend to operate on -
4
+ # is stored in the `contents` field of the Envelope. Any other data on the
5
+ # envelope is considered metadata. Metadata is mostly used by Timberline itself,
6
+ # but is also exposed to the end user in case they have any need for it.
7
+ #
8
+ # @attr [#to_json] contents the contents of the envelope; the message to be
9
+ # passed on the queue
10
+ # @attr [Hash] metadata the metadata information associated with the envelope
11
+ #
2
12
  class Envelope
3
13
 
14
+ # Given a JSON string representing an envelope, build an Envelope object
15
+ # with the appropriate data.
16
+ #
17
+ # @param [String] json_string the JSON string to parse
18
+ # @return [Envelope]
19
+ #
4
20
  def self.from_json(json_string)
5
21
  envelope = Envelope.new
6
22
  envelope.instance_variable_set("@metadata", JSON.parse(json_string))
@@ -11,16 +27,30 @@ class Timberline
11
27
  attr_accessor :contents
12
28
  attr_reader :metadata
13
29
 
30
+ # Instantiates an Envelope with no metadata and nil contents.
31
+ # @return [Envelope]
32
+ #
14
33
  def initialize
15
34
  @metadata = {}
16
35
  end
17
36
 
37
+ # Builds a JSON string version of the envelope.
38
+ #
39
+ # @raise [MissingContentException] if the envelope is empty (has no contents)
40
+ # @return [String] a JSON representation of the envelope
41
+ #
18
42
  def to_s
19
43
  raise MissingContentException if contents.nil? || contents.empty?
20
44
 
21
45
  JSON.unparse(build_envelope_hash)
22
46
  end
23
47
 
48
+ # Passes any missing methods on to the metadata hash to provide better access.
49
+ # @example Easily read from metadata
50
+ # some_envelope.origin_queue # returns metadata["origin_queue"]
51
+ # @example Easily write to metadata
52
+ # some_envelope.origin_queue = "test_queue" # sets metadata["origin_queue"] to "test_queue"
53
+ #
24
54
  def method_missing(method_name, *args)
25
55
  method_name = method_name.to_s
26
56
  if method_name[-1] == "="
@@ -41,7 +71,4 @@ class Timberline
41
71
  end
42
72
 
43
73
  end
44
-
45
- class MissingContentException < Exception
46
- end
47
74
  end
@@ -0,0 +1,17 @@
1
+ class Timberline
2
+
3
+ # Used to indicate that an Envelope does not yet have contents, but is being
4
+ # operated on as though it should.
5
+ #
6
+ class MissingContentException < Exception; end
7
+
8
+ # Raised to indicate that the item currently being processed was retried.
9
+ # Prevents Workers from treating the item as a success.
10
+ #
11
+ class ItemRetried < Exception; end
12
+
13
+ # Raised to indicate that the item currently being processed experienced a
14
+ # fatal error. Prevents Workers from treating the item as a success.
15
+ #
16
+ class ItemErrored < Exception; end
17
+ end
@@ -1,7 +1,29 @@
1
1
  class Timberline
2
+ # Queue is the heart and soul of Timberline, which makes sense
3
+ # considering that it's a queueing library. This object represents
4
+ # a queue in redis (really just a list of strings) and is responsible
5
+ # for reading to the queue, writing from the queue, maintaining queue
6
+ # statistics, and managing other queue actions (like pausing and deleting).
7
+ #
8
+ # @attr_reader [String] queue_name the name of this queue
9
+ # @attr_reader [Integer] read_timeout how long this queue should wait, in
10
+ # seconds, before determining that there isn't anything to read off of the
11
+ # queue.
12
+ #
2
13
  class Queue
3
14
  attr_reader :queue_name, :read_timeout
4
15
 
16
+ # Build a new Queue object.
17
+ #
18
+ # @param [String] queue_name the redis queue that this object should represent
19
+ # @param [Hash] opts the options for creating this queue
20
+ # @option opts [Integer] :read_timeout the read_timeout for this queue.
21
+ # defaults to 0 (which effectively disables the timeout).
22
+ # @option opts [boolean] :hidden whether this queue should be hidden from
23
+ # Timberline's #all_queues list. Defaults to false.
24
+ # @raise [ArgumentError] if queue_name is not provided
25
+ # @return [Queue]
26
+ #
5
27
  def initialize(queue_name, opts = {})
6
28
  read_timeout = opts.fetch(:read_timeout, 0)
7
29
  hidden = opts.fetch(:hidden, false)
@@ -16,6 +38,11 @@ class Timberline
16
38
  end
17
39
  end
18
40
 
41
+ # Delete this queue, removing it from redis and all other references to it
42
+ # from Timberline.
43
+ #
44
+ # @return as Redis#srem
45
+ #
19
46
  def delete
20
47
  @redis.del @queue_name
21
48
  @redis.keys("#{@queue_name}:*").each do |key|
@@ -24,10 +51,21 @@ class Timberline
24
51
  @redis.srem "timberline_queue_names", @queue_name
25
52
  end
26
53
 
54
+ # The current number of items on the queue waiting to be processed.
55
+ #
56
+ # @return [Integer]
57
+ #
27
58
  def length
28
59
  @redis.llen @queue_name
29
60
  end
30
61
 
62
+ # Uses a blocking read from redis to pull the next item off the queue. If
63
+ # the queue is paused, this method will block until the queue is unpaused,
64
+ # at which point it will move on to the blocking read.
65
+ #
66
+ # @return [Timberline::Envelope] the Envelope representation of the item
67
+ # that was pulled off the queue, or nil if the read timed out.
68
+ #
31
69
  def pop
32
70
  block_while_paused
33
71
 
@@ -40,6 +78,14 @@ class Timberline
40
78
  end
41
79
  end
42
80
 
81
+ # Pushes the specified data onto the queue.
82
+ #
83
+ # @param [#to_json, Timberline::Envelope] contents either contents that can
84
+ # be converted to JSON and stuffed in an Envelope, or an Envelope itself
85
+ # that needs to be put on the queue.
86
+ # @param [Hash] metadata metadata that will be attached to the envelope for
87
+ # contents.
88
+ #
43
89
  def push(contents, metadata = {})
44
90
  case contents
45
91
  when Envelope
@@ -49,34 +95,70 @@ class Timberline
49
95
  end
50
96
  end
51
97
 
98
+ # Puts this queue into paused mode.
99
+ # @see Timberline::Queue#pop
100
+ #
52
101
  def pause
53
102
  @redis[attr("paused")] = "true"
54
103
  end
55
104
 
105
+ # Takes this queue back out of paused mode.
106
+ # @see Timberline::Queue#pop
107
+ #
56
108
  def unpause
57
109
  @redis[attr("paused")] = "false"
58
110
  end
59
111
 
112
+ # Indicates whether or not this queue is currently in paused mode.
113
+ # @return [boolean]
114
+ #
60
115
  def paused?
61
116
  @redis[attr("paused")] == "true"
62
117
  end
63
118
 
119
+ # Given a key, create a string namespaced to this queue name.
120
+ # This method is used to keep redis keys tidy.
121
+ #
122
+ # @return [String]
123
+ #
64
124
  def attr(key)
65
125
  "#{@queue_name}:#{key}"
66
126
  end
67
127
 
128
+ # The number of items that have encountered fatal errors on the queue
129
+ # during the last [stat_timeout] minutes.
130
+ #
131
+ # @return [Integer]
132
+ #
68
133
  def number_errors
69
134
  Timberline.redis.xcard attr("error_stats")
70
135
  end
71
136
 
137
+ # The number of items that have been retried on the queue
138
+ # during the last [stat_timeout] minutes.
139
+ #
140
+ # @return [Integer]
141
+ #
72
142
  def number_retries
73
143
  Timberline.redis.xcard attr("retry_stats")
74
144
  end
75
145
 
146
+ # The number of items that were processed successfully for this queue
147
+ # during the last [stat_timeout] minutes.
148
+ #
149
+ # @return [Integer]
150
+ #
76
151
  def number_successes
77
152
  Timberline.redis.xcard attr("success_stats")
78
153
  end
79
154
 
155
+ # Given all of the successful jobs that were executed in the last
156
+ # [stat_timeout] minutes, determine how long on average those jobs
157
+ # took to execute.
158
+ #
159
+ # @return [Float] the average execution time for successful jobs in the last
160
+ # [stat_timeout] minutes.
161
+ #
80
162
  def average_execution_time
81
163
  successes = Timberline.redis.xmembers(attr("success_stats")).map { |item| Envelope.from_json(item)}
82
164
  times = successes.map do |item|
@@ -96,6 +178,15 @@ class Timberline
96
178
  end
97
179
  end
98
180
 
181
+ # Given an item that needs to be retried, increment the retry count,
182
+ # add any appropriate metadata about the retry, and push it back onto
183
+ # the queue. If the item has already been retried the maximum number of
184
+ # times, pass it on to error_item instead.
185
+ #
186
+ # @see Timberline::Queue#error_item
187
+ #
188
+ # @param [Envelope] item an item that needs to be retried
189
+ #
99
190
  def retry_item(item)
100
191
  if (item.retries < Timberline.max_retries)
101
192
  item.retries += 1
@@ -107,26 +198,50 @@ class Timberline
107
198
  end
108
199
  end
109
200
 
201
+ # Given an item that errored out in processing, add any appropriate metadata
202
+ # about the error, track it as a statistic, and push it onto the error queue.
203
+ #
204
+ # @param [Envelope] item an item that has fatally errored
205
+ #
110
206
  def error_item(item)
111
207
  item.fatal_error_at = Time.now.to_f
112
208
  add_error_stat(item)
113
209
  self.error_queue.push(item)
114
210
  end
115
211
 
212
+ # Stores an item from the queue that was retried so we can keep track
213
+ # of things like how many retries have been attempted on this queue, etc.
214
+ #
215
+ # @param [Envelope] item an item that fatally errored on this queue
216
+ #
116
217
  def add_retry_stat(item)
117
218
  add_stat_for_key(attr("retry_stats"), item)
118
219
  end
119
220
 
221
+ # Stores an item from the queue that fatally errored so we can keep track
222
+ # of things like how many errors have occurred on this queue, etc.
223
+ #
224
+ # @param [Envelope] item an item that fatally errored on this queue
225
+ #
120
226
  def add_error_stat(item)
121
227
  add_stat_for_key(attr("error_stats"), item)
122
228
  end
123
229
 
230
+ # Stores a successfully processed queue item as a statistic so we can keep
231
+ # track of things like average execution time, number of successes, etc.
232
+ #
233
+ # @param [Envelope] item an item that was processed successfully for this
234
+ # queue
235
+ #
124
236
  def add_success_stat(item)
125
237
  add_stat_for_key(attr("success_stats"), item)
126
238
  rescue Exception => e
127
239
  $stderr.puts "Success Stat Error: #{e.inspect}, Item: #{item.inspect}"
128
240
  end
129
241
 
242
+ # @return [Timberline::Queue] a (hidden) Queue object where this queue's
243
+ # errors are pushed.
244
+ #
130
245
  def error_queue
131
246
  @error_queue ||= Timberline.queue(attr("errors"), hidden: true)
132
247
  end
@@ -1,3 +1,4 @@
1
1
  class Timberline
2
- VERSION = "0.6.0"
2
+ # The current canonical version for Timberline.
3
+ VERSION = "0.7.0"
3
4
  end
@@ -0,0 +1,74 @@
1
+ class Timberline
2
+ # Worker is the base class for Timberline Workers. It defines the basics for
3
+ # processing items off of a queue; the idea is that creating your own worker
4
+ # is as easy as extending Worker and implementing #process_item. You can also
5
+ # override #keep_watching? and #initialize to provide your own custom behavior
6
+ # easily, although this is not necessary.
7
+ #
8
+ class Worker
9
+ # Creates a new worker that will watch a specific queue.
10
+ # @param [String] queue_name the name of the queue to watch.
11
+ #
12
+ def initialize(queue_name)
13
+ @queue = Queue.new(queue_name)
14
+ end
15
+
16
+ # Run the watch loop for this worker. As long as #keep_watching?
17
+ # returns true, this will pop items off the queue and process them
18
+ # with #process_item. This method is also responsible for managing
19
+ # some extra timberline metadata (tracking when processing starts and
20
+ # stops, for example) and shouldn't typically be overridden when you
21
+ # define your own worker.
22
+ #
23
+ def watch
24
+ while(keep_watching?)
25
+ item = @queue.pop
26
+ item.started_processing_at = Time.now.to_f
27
+
28
+ begin
29
+ process_item(item)
30
+ rescue ItemRetried, ItemErrored
31
+ next
32
+ end
33
+
34
+ item.finished_processing_at = Time.now.to_f
35
+ @queue.add_success_stat(item)
36
+ end
37
+ end
38
+
39
+ # Given an item off of the queue, process it appropriately.
40
+ # Not implemented in Worker, as Worker is just a base class.
41
+ #
42
+ def process_item(item)
43
+ raise NotImplementedError
44
+ end
45
+
46
+ # Determine whether or not the worker loop in #watch should continue
47
+ # executing. By default this is always true.
48
+ #
49
+ # @return [boolean]
50
+ def keep_watching?
51
+ true
52
+ end
53
+
54
+ # Given an item this worker is processing, have the queue mark it
55
+ # as fatally errored and raise an ItemErrored exception so that the
56
+ # watch loop can process it correctly
57
+ #
58
+ # @raise [Timberline::ItemErrored]
59
+ def error_item(item)
60
+ @queue.error_item(item)
61
+ raise Timberline::ItemErrored
62
+ end
63
+
64
+ # Given an item this worker is processing, have the queue mark it
65
+ # attempt to retry it and raise an ItemRetried exception so that the
66
+ # watch loop can process it correctly
67
+ #
68
+ # @raise [Timberline::ItemRetried]
69
+ def retry_item(item)
70
+ @queue.retry_item(item)
71
+ raise Timberline::ItemRetried
72
+ end
73
+ end
74
+ end
data/lib/timberline.rb CHANGED
@@ -7,16 +7,35 @@ require 'redis-namespace'
7
7
  require 'redis-expiring-set/monkeypatch'
8
8
 
9
9
  require_relative "timberline/version"
10
+ require_relative "timberline/exceptions"
10
11
  require_relative "timberline/config"
11
12
  require_relative "timberline/queue"
12
13
  require_relative "timberline/envelope"
13
14
 
15
+ require_relative "timberline/worker"
16
+ require_relative "timberline/anonymous_worker"
17
+
18
+ # The Timberline class serves as a base namespace for Timberline libraries, but
19
+ # also provides some convenience methods for accessing queues and quickly and
20
+ # easily processing items.
21
+ #
14
22
  class Timberline
15
23
  class << self
16
24
  attr_reader :config
17
25
  attr_accessor :watch_proc
18
26
  end
19
27
 
28
+ # Update the redis server that Timberline uses for its connections.
29
+ #
30
+ # If Timberline has not already been configured, this method will initialize
31
+ # a new Timberline::Config first.
32
+ #
33
+ # @param [Redis, Redis::Namespace, nil] server if Redis, wraps it in a namespace.
34
+ # if Redis::Namespace, uses that namespace directly. If nil, clears out any reference
35
+ # to the existing redis server.
36
+ #
37
+ # @raise [StandardError] if server is not an instance of Redis, Redis::Namespace, or nil.
38
+ #
20
39
  def self.redis=(server)
21
40
  initialize_if_necessary
22
41
  if server.is_a? Redis
@@ -30,6 +49,15 @@ class Timberline
30
49
  end
31
50
  end
32
51
 
52
+ # Obtain a reference to the redis connection that Timberline is using.
53
+ #
54
+ # If Timberline has not already been configured, this method will initialize
55
+ # a new Timberline::Config first.
56
+ #
57
+ # If a Redis connection has not yet been established, this method will establish one.
58
+ #
59
+ # @return [Redis::Namespace]
60
+ #
33
61
  def self.redis
34
62
  initialize_if_necessary
35
63
  if @redis.nil?
@@ -39,107 +67,93 @@ class Timberline
39
67
  @redis
40
68
  end
41
69
 
70
+ # @return [Array<Timberline::Queue>] a list of all non-hidden queues for this
71
+ # instance of Timberline
42
72
  def self.all_queues
43
73
  Timberline.redis.smembers("timberline_queue_names").map { |name| queue(name) }
44
74
  end
45
75
 
76
+ # Convenience method to create a new Queue object
77
+ # @see Timberline::Queue#initialize
46
78
  def self.queue(queue_name, opts = {})
47
79
  Queue.new(queue_name, opts)
48
80
  end
49
81
 
82
+ # Convenience method to push an item onto a queue
83
+ # @see Timberline::Queue#push
50
84
  def self.push(queue_name, data, metadata={})
51
85
  queue(queue_name).push(data, metadata)
52
86
  end
53
87
 
88
+ # Convenience method to retry a queue item
89
+ # @see Timberline::Queue#retry_item
54
90
  def self.retry_item(item)
55
91
  origin_queue = queue(item.origin_queue)
56
92
  origin_queue.retry_item(item)
57
93
  end
58
94
 
95
+ # Convenience method to error out a queue item
96
+ # @see Timberline::Queue#error_item
59
97
  def self.error_item(item)
60
98
  origin_queue = queue(item.origin_queue)
61
99
  origin_queue.error_item(item)
62
100
  end
63
101
 
102
+ # Convenience method to pause a Queue by name.
103
+ # @see Timberline::Queue#pause
64
104
  def self.pause(queue_name)
65
105
  queue(queue_name).pause
66
106
  end
67
107
 
108
+ # Convenience method to unpause a Queue by name.
109
+ # @see Timberline::Queue#unpause
68
110
  def self.unpause(queue_name)
69
111
  queue(queue_name).unpause
70
112
  end
71
113
 
114
+ # Method for providing custom configuration by yielding the config object.
115
+ # Lazy-loads the Timberline configuration.
116
+ # @param [Block] block a block that accepts and manipulates a Timberline::Config
117
+ #
72
118
  def self.configure(&block)
73
119
  initialize_if_necessary
74
120
  yield @config
75
121
  end
76
122
 
123
+ # Lazy-loads the Timberline configuration.
124
+ # @return [Integer] the maximum number of retries
77
125
  def self.max_retries
78
126
  initialize_if_necessary
79
127
  @config.max_retries
80
128
  end
81
129
 
130
+ # Lazy-loads the Timberline configuration.
131
+ # @return [Integer] the stat_timeout expressed in minutes
82
132
  def self.stat_timeout
83
133
  initialize_if_necessary
84
134
  @config.stat_timeout
85
135
  end
86
136
 
137
+ # Lazy-loads the Timberline configuration.
138
+ # @return [Integer] the stat_timeout expressed in seconds
87
139
  def self.stat_timeout_seconds
88
140
  initialize_if_necessary
89
141
  @config.stat_timeout * 60
90
142
  end
91
143
 
144
+ # Create and start a new AnonymousWorker with the given
145
+ # queue_name and block. Convenience method.
146
+ #
147
+ # @param [String] queue_name the name of the queue to watch.
148
+ # @param [Block] block the block to execute for each queue item
149
+ # @see Timberline::AnonymousWorker#watch
150
+ #
92
151
  def self.watch(queue_name, &block)
93
- queue = queue(queue_name)
94
- while(self.watch?)
95
- item = queue.pop
96
- fix_binding(block)
97
- item.started_processing_at = Time.now.to_f
98
-
99
- begin
100
- block.call(item, self)
101
- rescue ItemRetried
102
- queue.add_retry_stat(item)
103
- rescue ItemErrored
104
- queue.add_error_stat(item)
105
- else
106
- item.finished_processing_at = Time.now.to_f
107
- queue.add_success_stat(item)
108
- end
109
- end
152
+ Timberline::AnonymousWorker.new(queue_name, &block).watch
110
153
  end
111
154
 
112
- private
155
+ private
113
156
  def self.initialize_if_necessary
114
157
  @config ||= Config.new
115
158
  end
116
-
117
- # Hacky-hacky. I like the idea of calling retry_item(item) and
118
- # error_item(item)
119
- # directly from the watch block, but this seems ugly. There may be a better
120
- # way to do this.
121
- def self.fix_binding(block)
122
- binding = block.binding
123
- binding.eval <<-HERE
124
- def retry_item(item)
125
- Timberline.retry_item(item)
126
- raise Timberline::ItemRetried
127
- end
128
-
129
- def error_item(item)
130
- Timberline.error_item(item)
131
- raise Timberline::ItemErrored
132
- end
133
- HERE
134
- end
135
-
136
- def self.watch?
137
- watch_proc.nil? ? true : watch_proc.call
138
- end
139
-
140
- class ItemRetried < Exception
141
- end
142
-
143
- class ItemErrored < Exception
144
- end
145
159
  end
@@ -316,45 +316,4 @@ describe Timberline do
316
316
  end
317
317
  end
318
318
  end
319
-
320
- describe ".watch" do
321
- let(:queue) { Timberline.queue("test_queue") }
322
- let(:queue_name) { queue.queue_name }
323
- let(:success_item) { Timberline::Envelope.from_json(Timberline.redis.xmembers(queue.attr("success_stats")).first) }
324
- let(:retried_item) { Timberline::Envelope.from_json(Timberline.redis.xmembers(queue.attr("retry_stats")).first) }
325
- let(:errored_item) { Timberline::Envelope.from_json(Timberline.redis.xmembers(queue.attr("error_stats")).first) }
326
-
327
- before do
328
- # Make sure that the watch doesn't run forever.
329
- Timberline.watch_proc = lambda { queue.length > 0 }
330
- Timberline.push(queue_name, "Hey There!")
331
- end
332
-
333
- context "If the item can be processed successfully" do
334
- it "logs the success of the item" do
335
- expect_any_instance_of(Timberline::Queue).to receive(:add_success_stat)
336
- Timberline.watch(queue_name) do |item|
337
- # don't do anything
338
- end
339
- end
340
- end
341
-
342
- context "If the item is retried" do
343
- it "logs that the item was retried" do
344
- expect_any_instance_of(Timberline::Queue).to receive(:add_retry_stat)
345
- Timberline.watch(queue_name) do |item|
346
- raise Timberline::ItemRetried
347
- end
348
- end
349
- end
350
-
351
- context "If the item can be processed successfully" do
352
- it "logs that the item was retried" do
353
- expect_any_instance_of(Timberline::Queue).to receive(:add_error_stat)
354
- Timberline.watch(queue_name) do |item|
355
- raise Timberline::ItemErrored
356
- end
357
- end
358
- end
359
- end
360
319
  end
data/timberline.gemspec CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |s|
24
24
  s.add_runtime_dependency "trollop"
25
25
  s.add_runtime_dependency "daemons"
26
26
 
27
+ s.add_development_dependency "yard"
27
28
  s.add_development_dependency "rake"
28
29
  s.add_development_dependency "rspec", '~> 3.0.0.rc1'
29
30
  s.add_development_dependency "pry"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timberline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tommy Morgan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-06 00:00:00.000000000 Z
11
+ date: 2014-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - '>='
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: yard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: rake
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -137,16 +151,23 @@ files:
137
151
  - .rspec
138
152
  - .ruby-gemset
139
153
  - .travis.yml
154
+ - .yardoc/checksums
155
+ - .yardoc/object_types
156
+ - .yardoc/objects/root.dat
157
+ - .yardoc/proxy_types
140
158
  - CHANGELOG
141
159
  - Gemfile
142
160
  - README.markdown
143
161
  - Rakefile
144
162
  - bin/timberline
145
163
  - lib/timberline.rb
164
+ - lib/timberline/anonymous_worker.rb
146
165
  - lib/timberline/config.rb
147
166
  - lib/timberline/envelope.rb
167
+ - lib/timberline/exceptions.rb
148
168
  - lib/timberline/queue.rb
149
169
  - lib/timberline/version.rb
170
+ - lib/timberline/worker.rb
150
171
  - spec/config/test_config.yaml
151
172
  - spec/lib/timberline/config_spec.rb
152
173
  - spec/lib/timberline/envelope_spec.rb
@@ -181,3 +202,4 @@ specification_version: 4
181
202
  summary: Timberline is a simple and extensible queuing system built in Ruby and backed
182
203
  by Redis.
183
204
  test_files: []
205
+ has_rdoc: