timberline 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.markdown +214 -0
- data/Rakefile +9 -0
- data/bin/timberline +37 -0
- data/lib/timberline/config.rb +45 -0
- data/lib/timberline/envelope.rb +47 -0
- data/lib/timberline/queue.rb +42 -0
- data/lib/timberline/queue_manager.rb +84 -0
- data/lib/timberline/version.rb +3 -0
- data/lib/timberline.rb +65 -0
- data/test/fake_rails/config/timberline.yaml +6 -0
- data/test/partial_minispec.rb +229 -0
- data/test/test_config.yaml +6 -0
- data/test/test_helper.rb +15 -0
- data/test/unit/test_config.rb +115 -0
- data/test/unit/test_envelope.rb +67 -0
- data/test/unit/test_queue.rb +75 -0
- data/test/unit/test_queue_manager.rb +154 -0
- data/test/unit/test_timberline.rb +65 -0
- data/timberline.gemspec +28 -0
- metadata +124 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
# Timberline
|
2
|
+
|
3
|
+
## Purpose
|
4
|
+
|
5
|
+
Timberline is a dead-simple queuing service written in Ruby and backed by Redis.
|
6
|
+
It makes a few assumptions about how you'd like to handle your queues and what
|
7
|
+
kind of issues you might be dealing with:
|
8
|
+
|
9
|
+
1. Timberline assumes that you want to be able to programmatically retry some
|
10
|
+
failed jobs, and that you want to keep track of jobs that totally errored out
|
11
|
+
so that you can try them again.
|
12
|
+
|
13
|
+
2. Timberline assumes that you want to queue data, and not actions. You can have
|
14
|
+
one app that puts data onto the queue and another app that reads data from
|
15
|
+
the queue, and the only thing they have to have in common (aside from knowing
|
16
|
+
what the data means) is that they both include Timberline.
|
17
|
+
|
18
|
+
3. Timberline assumes that it's preferable, if not important to you, to process
|
19
|
+
jobs as fast as you possibly can. To that end, Timberline uses blocking reads
|
20
|
+
in Redis to pull jobs off of the queue as soon as they're available.
|
21
|
+
|
22
|
+
## Concepts
|
23
|
+
|
24
|
+
### Retries
|
25
|
+
|
26
|
+
Sometimes jobs just fail because of something that was outside of your control.
|
27
|
+
Maybe there was a glitch and your HTTP connection to PayPal didn't go through,
|
28
|
+
or maybe Github is down right now, or... whatever. In these situations it makes
|
29
|
+
sense to re-queue jobs and let them retry - just don't let them do it forever or
|
30
|
+
they may never leave. Timberline is designed to make retrying jobs in these
|
31
|
+
circumstances super-easy.
|
32
|
+
|
33
|
+
### Errors
|
34
|
+
|
35
|
+
On the other hand, sometimes your jobs deserved to fail. Maybe there was a bug
|
36
|
+
in your processor code, or maybe a user was able to sneak bad data past you. In
|
37
|
+
any event, Timberline maintains an error queue where jobs go when they're
|
38
|
+
explicitly marked as bad jobs, or when they've been retried the maximum number
|
39
|
+
of times. You can then check the jobs out and resubmit them to their original
|
40
|
+
queue after you fix the issue.
|
41
|
+
|
42
|
+
### The Envelope
|
43
|
+
|
44
|
+
Sounds SOAPy, I know. The envelope is a simple object that wraps the data you
|
45
|
+
want to put on the queue - it's responsible for tracking things like the job ID,
|
46
|
+
the queue it was put on, how many times it's been retried, etc., etc. It's also
|
47
|
+
accessible to both the queue processor and whatever is putting jobs on the
|
48
|
+
queue, so if you want to be able to check in on the administrative details (or
|
49
|
+
add some of your own) this is a great place to do it instead of muddying up the
|
50
|
+
meat of your message.
|
51
|
+
|
52
|
+
## Usage
|
53
|
+
|
54
|
+
Timberline is designed to be as easy to work with as possible, and operates almost
|
55
|
+
like a DSL for interacting with stuff on the queue.
|
56
|
+
|
57
|
+
### Configuration
|
58
|
+
|
59
|
+
There are a few things that you probably want to be able to configure in
|
60
|
+
Timberline. At the moment this is largely stuff related to the redis server
|
61
|
+
connection, but you can also configure a namespace for your redis queues
|
62
|
+
(defaults to "timberline") and a maximum number of retry attempts for jobs in the
|
63
|
+
queue (more on that later). There are 3 ways to configure Timberline:
|
64
|
+
|
65
|
+
1. The most direct way is to configure Timberline via ruby code as follows:
|
66
|
+
|
67
|
+
Timberline.config do |c|
|
68
|
+
c.database = 1
|
69
|
+
c.host = "192.168.1.105"
|
70
|
+
c.port = 12345
|
71
|
+
c.password = "foobar"
|
72
|
+
end
|
73
|
+
|
74
|
+
...As long as you run this block before you attempt to access your queues,
|
75
|
+
your settings will all take effect. Redis defaults will be used if you omit
|
76
|
+
anything.
|
77
|
+
|
78
|
+
2. If you're including Timberline in a Rails app, there's a convenient way to
|
79
|
+
configure it that should fit in with the rest of your app - if you include a
|
80
|
+
yaml file named timberline.yaml in your config directory, Timberline will
|
81
|
+
automatically detect it and load it up. The syntax for this file is
|
82
|
+
shockingly boring:
|
83
|
+
|
84
|
+
database: 1
|
85
|
+
host: 192.168.1.105
|
86
|
+
port: 12345
|
87
|
+
password: foobar
|
88
|
+
|
89
|
+
3. Like the yaml format but you're not using Rails? Don't worry, just write your
|
90
|
+
yaml file and set the TIMBERLINE\_YAML constant inside your app like so:
|
91
|
+
|
92
|
+
TIMBERLINE_YAML = 'path/to/your/yaml/file.yaml'
|
93
|
+
|
94
|
+
### Pushing jobs onto a queue
|
95
|
+
|
96
|
+
To push a job onto the queue you'll want to make use of the `Timberline#push`
|
97
|
+
method, like so:
|
98
|
+
|
99
|
+
Timberline.push "queue_name", data, { :other_data => some_stuff }
|
100
|
+
|
101
|
+
`queue_name` is the name of the queue you want to push data onto; data is the
|
102
|
+
data you want to push onto the queue (remember that this all gets converted to
|
103
|
+
JSON, so you probably want to stick to things that represent well as strings),
|
104
|
+
and the optional third argument is a hash of any extra parameters you want to
|
105
|
+
include in the job's envelope.
|
106
|
+
|
107
|
+
### Reading from a queue
|
108
|
+
|
109
|
+
Reading from a queue is pretty simple in Timberline. You can simply write
|
110
|
+
something like the following:
|
111
|
+
|
112
|
+
Timberline.watch "queue_name" do |job|
|
113
|
+
begin
|
114
|
+
puts job.other_data
|
115
|
+
doSomethingWithThisStuff(job.contents)
|
116
|
+
rescue SomeTransientError
|
117
|
+
retry_job(job)
|
118
|
+
rescue SomeFatalError
|
119
|
+
error_job(job)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
You will, in all likelihood, be writing more complicated stuff than this, of
|
124
|
+
course. But you call Timberline.watch and provide it with a queue name and a block
|
125
|
+
that will be called for each job as Timberline reads them off of the queue. Things
|
126
|
+
to note:
|
127
|
+
|
128
|
+
- The variable that will be passed into the block is the envelope for the job.
|
129
|
+
To read what you actually posted into the queue, use job.contents.
|
130
|
+
- The envelope makes use of method\_missing to give you easy access to your
|
131
|
+
metadata (note that we used job.other\_data to access the other\_data property
|
132
|
+
that we added in the pushing example).
|
133
|
+
- retry\_job and error\_job are exactly what they seem like - they either try to
|
134
|
+
retry the job in the event of a transient error, or put it on the error queue
|
135
|
+
for processing if a more fatal error occurs.
|
136
|
+
|
137
|
+
### The error queue
|
138
|
+
|
139
|
+
If you want to interact with the error queue directly, it's accessible via
|
140
|
+
`Timberline#error_queue`. You can pop items directly off of the queue to operate
|
141
|
+
on them if you want, or you could write a queue processor that reads off of that
|
142
|
+
queue (its queue name should always be "Timberline\_errors").
|
143
|
+
|
144
|
+
### Using the binary
|
145
|
+
|
146
|
+
In order to make reading off of the queue easier, there's a binary named
|
147
|
+
`Timberline` included with this gem.
|
148
|
+
|
149
|
+
Example:
|
150
|
+
|
151
|
+
# timberline_sample.rb
|
152
|
+
TIMBERLINE_YAML = "timberline_sample.yaml"
|
153
|
+
|
154
|
+
watch "sample_queue" do |job|
|
155
|
+
puts job.contents
|
156
|
+
end
|
157
|
+
|
158
|
+
The above file, when executed via `timberline timberline_sample.rb`, will print out
|
159
|
+
the value of any object put on the queue. If no objects are on the queue it will
|
160
|
+
block until either the process is killed, or until something is added to the
|
161
|
+
queue.
|
162
|
+
|
163
|
+
There are some options to the Timberline binary that you may find helpful -
|
164
|
+
`timberline --help` for more.
|
165
|
+
|
166
|
+
## TODO
|
167
|
+
|
168
|
+
Still to be done:
|
169
|
+
|
170
|
+
- A simple Sinatra interface for monitoring the statuses of queues and
|
171
|
+
observing/resubmitting errored-out jobs.
|
172
|
+
- Binary updates - the binary should probably fork processes for each job
|
173
|
+
that it tries to process so that it's more robust.
|
174
|
+
- DSL improvements - the DSL-ish setup for Timberline could probably use some
|
175
|
+
updates to be both more obvious and easier to use.
|
176
|
+
- Documentation - need to get YARD docs added so that the API is more completely
|
177
|
+
documented. For the time being, though, there are some fairly comprehensive
|
178
|
+
test suites.
|
179
|
+
- Timing - it would be crazy useful to be able to automatically log per-queue
|
180
|
+
statistics about how long jobs are taking. Definitely something like an "over
|
181
|
+
the last 5 minutes/past 1000 jobs" stat would be useful, but we may also be
|
182
|
+
interested in some kind of lifetime average.
|
183
|
+
|
184
|
+
## Future
|
185
|
+
|
186
|
+
Stuff that would be cool but isn't quite on the radar yet:
|
187
|
+
|
188
|
+
- Client libraries for other languages - maybe you want to put stuff onto the
|
189
|
+
queue using Ruby but read from it in, say, Java. Or vice-versa. Or whatever.
|
190
|
+
The queue data should be platform-agnostic.
|
191
|
+
|
192
|
+
## Contributions
|
193
|
+
|
194
|
+
If Timberline interests you and you think you might want to contribute, hit me up
|
195
|
+
over Github. You can also just fork it and make some changes, but there's a
|
196
|
+
better chance that your work won't be duplicated or rendered obsolete if you
|
197
|
+
check in on the current development status first.
|
198
|
+
|
199
|
+
## Development notes
|
200
|
+
|
201
|
+
You need Redis installed to do development on Timberline, and currently the test
|
202
|
+
suites assume that you're using the default configurations for Redis. That
|
203
|
+
should probably change, but it probably won't until someone needs it to.
|
204
|
+
|
205
|
+
Gem requirements/etc. should be handled by Bundler.
|
206
|
+
|
207
|
+
## License
|
208
|
+
Copyright (C) 2012 by Tommy Morgan
|
209
|
+
|
210
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
211
|
+
|
212
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
213
|
+
|
214
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/bin/timberline
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'daemons'
|
4
|
+
require 'trollop'
|
5
|
+
|
6
|
+
require 'timberline'
|
7
|
+
|
8
|
+
opts = Trollop::options do
|
9
|
+
version "timberline #{Timberline::VERSION} (c) 2012 Tommy Morgan"
|
10
|
+
banner <<-EOS
|
11
|
+
The timberline command-line interface allows you to easily start up Timberline queue watchers.
|
12
|
+
|
13
|
+
Usage:
|
14
|
+
timberline [options] <filename>
|
15
|
+
where [options] are:
|
16
|
+
EOS
|
17
|
+
|
18
|
+
opt :daemonize, "Run the queue listener as a daemon", :default => false
|
19
|
+
opt :log_output, "log the output to <filename>.output (only applicable in conjunction with the daemonize option)", :default => false
|
20
|
+
opt :config, "YAML config file to set up Timberline options",
|
21
|
+
:type => String, :default => nil
|
22
|
+
end
|
23
|
+
|
24
|
+
unless opts[:config].nil?
|
25
|
+
TIMBERLINE_YAML = opts[:config]
|
26
|
+
end
|
27
|
+
|
28
|
+
timberline_file = ARGV[0]
|
29
|
+
timberline_spec = File.read(timberline_file)
|
30
|
+
|
31
|
+
if opts[:daemonize]
|
32
|
+
puts "Entering daemon mode"
|
33
|
+
Daemons.daemonize({ :app_name => timberline_file, :log_output => opts[:log_output] })
|
34
|
+
end
|
35
|
+
|
36
|
+
puts "Listening..."
|
37
|
+
Timberline.class_eval(timberline_spec)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class Timberline
|
2
|
+
class Config
|
3
|
+
attr_accessor :database, :host, :port, :timeout, :password, :logger, :namespace, :max_retries
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
if defined? TIMBERLINE_YAML
|
7
|
+
if File.exists?(TIMBERLINE_YAML)
|
8
|
+
load_from_yaml(TIMBERLINE_YAML)
|
9
|
+
else
|
10
|
+
raise "Specified Timberline config file #{TIMBERLINE_YAML} is not present."
|
11
|
+
end
|
12
|
+
elsif defined? RAILS_ROOT
|
13
|
+
config_file = File.join(RAILS_ROOT, 'config', 'timberline.yaml')
|
14
|
+
if File.exists?(config_file)
|
15
|
+
load_from_yaml(config_file)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def namespace
|
21
|
+
@namespace ||= 'timberline'
|
22
|
+
end
|
23
|
+
|
24
|
+
def max_retries
|
25
|
+
@max_retries ||= 5
|
26
|
+
end
|
27
|
+
|
28
|
+
def redis_config
|
29
|
+
config = {}
|
30
|
+
|
31
|
+
{ :db => database, :host => host, :port => port, :timeout => timeout, :password => password, :logger => logger }.each do |name, value|
|
32
|
+
config[name] = value unless value.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
config
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_from_yaml(filename)
|
39
|
+
yaml_config = YAML.load_file(filename)
|
40
|
+
["database","host","port","timeout","password","logger","namespace"].each do |setting|
|
41
|
+
self.instance_variable_set("@#{setting}", yaml_config[setting])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Timberline
|
2
|
+
class Envelope
|
3
|
+
|
4
|
+
def self.from_json(json_string)
|
5
|
+
envelope = Envelope.new
|
6
|
+
envelope.instance_variable_set("@metadata", JSON.parse(json_string))
|
7
|
+
envelope.contents = envelope.metadata.delete("contents")
|
8
|
+
envelope
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :contents
|
12
|
+
attr_reader :metadata
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@metadata = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
raise MissingContentException if contents.nil? || contents.empty?
|
20
|
+
|
21
|
+
JSON.unparse(build_envelope_hash)
|
22
|
+
end
|
23
|
+
|
24
|
+
def method_missing(method_name, *args)
|
25
|
+
method_name = method_name.to_s
|
26
|
+
if method_name[-1] == "="
|
27
|
+
assign_var(method_name[0, method_name.size - 1], args.first)
|
28
|
+
else
|
29
|
+
return metadata[method_name]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def build_envelope_hash
|
36
|
+
{ :contents => contents }.merge(@metadata)
|
37
|
+
end
|
38
|
+
|
39
|
+
def assign_var(name, value)
|
40
|
+
@metadata[name] = value
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
class MissingContentException < Exception
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Timberline
|
2
|
+
class Queue
|
3
|
+
attr_reader :queue_name, :read_timeout
|
4
|
+
|
5
|
+
def initialize(queue_name, read_timeout= 0)
|
6
|
+
@queue_name = queue_name
|
7
|
+
@read_timeout = read_timeout
|
8
|
+
@redis = Timberline.redis
|
9
|
+
end
|
10
|
+
|
11
|
+
def length
|
12
|
+
@redis.llen @queue_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def pop
|
16
|
+
br_tuple = @redis.brpop(@queue_name, read_timeout)
|
17
|
+
envelope_string = br_tuple.nil? ? nil : br_tuple[1]
|
18
|
+
if envelope_string.nil?
|
19
|
+
nil
|
20
|
+
else
|
21
|
+
Envelope.from_json(envelope_string)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def push(item)
|
26
|
+
case item
|
27
|
+
when Envelope
|
28
|
+
@redis.lpush @queue_name, item
|
29
|
+
else
|
30
|
+
@redis.lpush @queue_name, wrap(item)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def wrap(item)
|
37
|
+
envelope = Envelope.new
|
38
|
+
envelope.contents = item
|
39
|
+
envelope
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
class Timberline
|
2
|
+
class QueueManager
|
3
|
+
attr_reader :queue_list
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@queue_list = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def error_queue
|
10
|
+
@error_queue ||= Queue.new("timberline_errors")
|
11
|
+
end
|
12
|
+
|
13
|
+
def queue(queue_name)
|
14
|
+
if @queue_list[queue_name].nil?
|
15
|
+
@queue_list[queue_name] = Queue.new(queue_name)
|
16
|
+
end
|
17
|
+
@queue_list[queue_name]
|
18
|
+
end
|
19
|
+
|
20
|
+
def push(queue_name, data, metadata={})
|
21
|
+
data = wrap(data, metadata.merge({:origin_queue => queue_name}))
|
22
|
+
queue(queue_name).push(data)
|
23
|
+
end
|
24
|
+
|
25
|
+
def retry_job(job)
|
26
|
+
if (job.retries < Timberline.max_retries)
|
27
|
+
job.retries += 1
|
28
|
+
job.last_tried_at = DateTime.now
|
29
|
+
queue(job.origin_queue).push(job)
|
30
|
+
else
|
31
|
+
error_job(job)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def error_job(job)
|
36
|
+
job.fatal_error_at = DateTime.now
|
37
|
+
error_queue.push(job)
|
38
|
+
end
|
39
|
+
|
40
|
+
def watch(queue_name, &block)
|
41
|
+
queue = queue(queue_name)
|
42
|
+
while(true)
|
43
|
+
job = queue.pop
|
44
|
+
fix_binding(block)
|
45
|
+
block.call(job, self)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def wrap(contents, metadata)
|
51
|
+
env = Envelope.new
|
52
|
+
env.contents = contents
|
53
|
+
metadata.each do |key, value|
|
54
|
+
env.send("#{key.to_s}=", value)
|
55
|
+
end
|
56
|
+
|
57
|
+
env.job_id = next_id_for_queue(metadata[:origin_queue])
|
58
|
+
env.retries = 0
|
59
|
+
env.submitted_at = DateTime.now
|
60
|
+
|
61
|
+
env
|
62
|
+
end
|
63
|
+
|
64
|
+
def next_id_for_queue(queue_name)
|
65
|
+
Timberline.redis.incr "#{queue_name}_id_seq"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Hacky-hacky. I like the idea of calling retry_job(job) and error_job(job)
|
69
|
+
# directly from the watch block, but this seems ugly. There may be a better
|
70
|
+
# way to do this.
|
71
|
+
def fix_binding(block)
|
72
|
+
binding = block.binding
|
73
|
+
binding.eval <<-HERE
|
74
|
+
def retry_job(job)
|
75
|
+
Timberline.retry_job(job)
|
76
|
+
end
|
77
|
+
|
78
|
+
def error_job(job)
|
79
|
+
Timberline.error_job(job)
|
80
|
+
end
|
81
|
+
HERE
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/timberline.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'json'
|
3
|
+
require 'logger'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
require 'redis'
|
7
|
+
require 'redis-namespace'
|
8
|
+
|
9
|
+
require_relative "timberline/version"
|
10
|
+
require_relative "timberline/config"
|
11
|
+
require_relative "timberline/queue"
|
12
|
+
require_relative "timberline/envelope"
|
13
|
+
require_relative "timberline/queue_manager"
|
14
|
+
|
15
|
+
class Timberline
|
16
|
+
class << self
|
17
|
+
extend Forwardable
|
18
|
+
|
19
|
+
def_delegators :@queue_manager, :error_job, :retry_job, :watch, :push
|
20
|
+
|
21
|
+
attr_reader :config
|
22
|
+
|
23
|
+
def redis=(server)
|
24
|
+
initialize_if_necessary
|
25
|
+
if server.is_a? Redis
|
26
|
+
@redis = Redis::Namespace.new(@config.namespace, :redis => server)
|
27
|
+
elsif server.is_a? Redis::Namespace
|
28
|
+
@redis = server
|
29
|
+
elsif server.nil?
|
30
|
+
@redis = nil
|
31
|
+
else
|
32
|
+
raise "Not a valid Redis connection."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def redis
|
37
|
+
initialize_if_necessary
|
38
|
+
if @redis.nil?
|
39
|
+
self.redis = Redis.new(@config.redis_config)
|
40
|
+
end
|
41
|
+
@redis
|
42
|
+
end
|
43
|
+
|
44
|
+
def config(&block)
|
45
|
+
initialize_if_necessary
|
46
|
+
yield @config
|
47
|
+
end
|
48
|
+
|
49
|
+
def max_retries
|
50
|
+
initialize_if_necessary
|
51
|
+
@config.max_retries
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
# Don't know if I like doing this, but we want the configuration to be
|
56
|
+
# lazy-loaded so as to be sure and give users a chance to set up their
|
57
|
+
# configurations.
|
58
|
+
def initialize_if_necessary
|
59
|
+
@config ||= Config.new
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
Timberline.instance_variable_set("@queue_manager", Timberline::QueueManager.new)
|