bluth 0.6.8 → 0.7.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.
data/CHANGES.txt CHANGED
@@ -1,5 +1,14 @@
1
1
  BLUTH, CHANGES
2
2
 
3
+ #### 0.7.0 (2011-03-04) ###############################
4
+
5
+ * ADDED: backtrace field to Bluth::Gob
6
+ * ADDED: Bluth::TimingBelt
7
+ * ADDED: Bluth::Queue.queues/entry_queues include TimingBelt queues
8
+ when Bluth::TimingBelt is defined.
9
+ * ADDED: replace-worker command
10
+
11
+
3
12
  #### 0.6.8 (2011-02-07) ###############################
4
13
 
5
14
  * FIXED: Remove use of calls to fineround in Worker
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :MAJOR: 0
3
- :MINOR: 6
4
- :PATCH: 8
3
+ :MINOR: 7
4
+ :PATCH: 0
data/bin/bluth CHANGED
@@ -68,14 +68,17 @@ class Bluth::CLI::Definition
68
68
 
69
69
  command :start_worker => Bluth::CLI
70
70
  command :start_scheduler => Bluth::CLI
71
-
71
+
72
72
  option :f, :force
73
73
  command :stop_workers => Bluth::CLI
74
74
  option :f, :force
75
75
  command :stop_worker => Bluth::CLI
76
76
  option :f, :force
77
77
  command :stop_scheduler => Bluth::CLI
78
-
78
+
79
+ about "Stop the oldest worker and start a new instance in its place."
80
+ command :replace_worker => Bluth::CLI
81
+
79
82
  command :workers => Bluth::CLI
80
83
  command :schedulers => Bluth::CLI
81
84
 
data/bluth.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{bluth}
8
- s.version = "0.6.8"
8
+ s.version = "0.7.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Delano Mandelbaum"]
12
- s.date = %q{2011-02-08}
12
+ s.date = %q{2011-03-04}
13
13
  s.default_executable = %q{bluth}
14
14
  s.description = %q{A Redis queuing system built on top of Familia}
15
15
  s.email = %q{delano@solutious.com}
@@ -29,23 +29,24 @@ Gem::Specification.new do |s|
29
29
  "lib/bluth.rb",
30
30
  "lib/bluth/cli.rb",
31
31
  "lib/bluth/test_helpers.rb",
32
+ "lib/bluth/timingbelt.rb",
32
33
  "lib/bluth/worker.rb",
33
34
  "lib/daemonizing.rb",
34
35
  "try/15_queue_try.rb",
35
36
  "try/16_worker_try.rb",
36
37
  "try/17_gob_try.rb",
37
38
  "try/18_handler_try.rb",
38
- "try/19_bluth_try.rb"
39
+ "try/19_bluth_try.rb",
40
+ "try/30_timingbelt_try.rb"
39
41
  ]
40
42
  s.homepage = %q{http://github.com/delano/bluth}
41
43
  s.rdoc_options = ["--charset=UTF-8"]
42
44
  s.require_paths = ["lib"]
43
45
  s.rubyforge_project = %q{bluth}
44
- s.rubygems_version = %q{1.3.7}
46
+ s.rubygems_version = %q{1.5.2}
45
47
  s.summary = %q{A Redis queuing system built on top of Familia (w/ daemons!)}
46
48
 
47
49
  if s.respond_to? :specification_version then
48
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
49
50
  s.specification_version = 3
50
51
 
51
52
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
data/lib/bluth.rb CHANGED
@@ -88,7 +88,7 @@ module Bluth
88
88
 
89
89
  require 'bluth/worker'
90
90
 
91
- module Queue # if this is a module the
91
+ module Queue
92
92
  include Familia
93
93
  prefix [:bluth, :queue]
94
94
  class_list :critical #, :class => Bluth::Gob
@@ -98,16 +98,41 @@ module Bluth
98
98
  class_list :successful
99
99
  class_list :failed
100
100
  class_list :orphaned
101
+ def self.create_queue name
102
+ unless queue(name)
103
+ q = Familia::List.new name, :parent => self
104
+ @queuecache[name.to_s.to_sym] = q
105
+ end
106
+ queue(name)
107
+ end
101
108
  class << self
102
109
  # The complete list of queues in the order they were defined
103
110
  def queues
104
- Bluth::Queue.class_lists.collect(&:name).collect do |qname|
111
+ qs = Bluth::Queue.class_lists.collect(&:name).collect do |qname|
105
112
  self.send qname
106
113
  end
114
+ if defined?(Bluth::TimingBelt)
115
+ notch_queues = Bluth::TimingBelt.priority.collect { |notch| notch.queue }
116
+ qs.insert 1, *notch_queues
117
+ end
118
+ qs
107
119
  end
108
120
  # The subset of queues that new jobs arrive in, in order of priority
109
121
  def entry_queues
110
- Bluth.priority.collect { |qname| self.send qname }
122
+ qs = Bluth.priority.collect { |qname| self.send qname }
123
+ if defined?(Bluth::TimingBelt)
124
+ notch_queues = Bluth::TimingBelt.priority.collect { |notch| notch.queue }
125
+ qs.insert 1, *notch_queues
126
+ end
127
+ qs
128
+ end
129
+ def queue name
130
+ if class_list? name.to_s.to_sym
131
+ self.send(name)
132
+ else
133
+ @queuecache ||= {}
134
+ @queuecache[name.to_s.to_sym]
135
+ end
111
136
  end
112
137
  end
113
138
 
@@ -115,9 +140,9 @@ module Bluth
115
140
  Bluth.priority = [:critical, :high, :low]
116
141
  end
117
142
 
118
- # Workers use a blocking pop and will wait for up to
119
- # Bluth.queuetimeout (seconds) before returnning nil.
120
- # Note that the queues are still processed in order.
143
+ # Workers use a blocking pop and will wait for up to
144
+ # Bluth.queuetimeout (seconds) before returnning nil.
145
+ # Note that the queues are still processed in order.
121
146
  # If all queues are empty, the first one to return a
122
147
  # value is use. See:
123
148
  #
@@ -137,6 +162,7 @@ module Bluth
137
162
  gob = nil
138
163
  begin
139
164
  order = Bluth::Queue.entry_queues.collect(&:rediskey)
165
+ Familia.ld " QUEUE ORDER: #{order.join(', ')}"
140
166
  order << Bluth.queuetimeout # We do it this way to support Ruby 1.8
141
167
  queue, gobid = *(Bluth::Queue.redis.send(meth, *order) || [])
142
168
  unless queue.nil?
@@ -179,12 +205,27 @@ module Bluth
179
205
  end
180
206
  end
181
207
 
182
- def enqueue(data={},q=nil)
183
- q = self.queue(q)
208
+ def engauge(data={}, notch=nil)
209
+ notch ||= Bluth::TimingBelt.notch 1
210
+ gob = create_job data
211
+ gob.notch = notch.name
212
+ gob.save
213
+ Familia.ld "ENNOTCHING: #{self} #{gob.jobid.short} to #{notch.rediskey}" if Familia.debug?
214
+ notch.add gob.jobid
215
+ gob
216
+ end
217
+
218
+ def create_job data={}
184
219
  gob = Gob.create generate_id(data), self, data
185
- gob.current_queue = q.name
186
220
  gob.created
187
221
  gob.attempts = 0
222
+ gob
223
+ end
224
+
225
+ def enqueue(data={}, q=nil)
226
+ q = self.queue(q) if q.nil? || Symbol === q
227
+ gob = create_job data
228
+ gob.current_queue = q.name
188
229
  gob.save
189
230
  Familia.ld "ENQUEUING: #{self} #{gob.jobid.short} to #{q}" if Familia.debug?
190
231
  q << gob.jobid
@@ -192,7 +233,7 @@ module Bluth
192
233
  end
193
234
  def queue(name=nil)
194
235
  @queue = name if name
195
- Bluth::Queue.send(@queue || :high)
236
+ Bluth::Queue.queue(@queue || :high)
196
237
  end
197
238
  def generate_id(*args)
198
239
  [self, Process.pid, Bluth.sysinfo.hostname, Time.now.to_f, *args].gibbler
@@ -222,6 +263,8 @@ module Bluth
222
263
  field :messages => Array
223
264
  field :attempts => Integer
224
265
  field :create_time => Float
266
+ field :backtrace
267
+ field :notch # populated only via TimingBelt
225
268
  field :stime => Float
226
269
  field :etime => Float
227
270
  field :current_queue => Symbol
data/lib/bluth/cli.rb CHANGED
@@ -45,17 +45,25 @@ module Bluth
45
45
  end
46
46
  end
47
47
 
48
- def stop_worker wid=nil,worker_class=Bluth::Worker
49
- wids = wid ? [wid] : @argv
48
+ def stop_worker wid=nil, worker_class=Bluth::Worker
50
49
  Bluth.connect
50
+ wids = wid ? [wid] : @argv
51
51
  wids.each do |wid|
52
52
  worker = worker_class.from_redis wid
53
53
  kill_worker worker, worker_class
54
54
  end
55
55
  end
56
56
 
57
+ def replace_worker worker_class=Bluth::Worker
58
+ Bluth.connect
59
+ @global.daemon = true
60
+ worker = worker_class.instances.first # grabs the oldest worker
61
+ kill_worker worker, worker_class
62
+ start_worker worker_class
63
+ end
64
+
57
65
  def workers worker_class=Bluth::Worker
58
- Familia.info worker_class.all.collect &:key
66
+ Familia.info worker_class.all.collect &:rediskey
59
67
  end
60
68
 
61
69
  private
@@ -0,0 +1,118 @@
1
+ require 'time'
2
+
3
+ module Bluth
4
+
5
+ module TimingBelt
6
+ include Familia
7
+ prefix [:bluth, :timingbelt]
8
+ # This module extends the Familia::Set that represents
9
+ # a notch. IOW, these are instance methods for notch objs.
10
+ module Notch
11
+ attr_accessor :stamp, :filter, :time
12
+ def next
13
+ skip
14
+ end
15
+ def prev
16
+ skip -1
17
+ end
18
+ def skip mins=1
19
+ time = Time.parse(stamp || '')
20
+ Bluth::TimingBelt.notch mins, filter, time
21
+ end
22
+ def queue
23
+ Bluth::Queue.create_queue stamp
24
+ end
25
+ def -(other)
26
+ ((self.time - other.time)/60).to_i
27
+ end
28
+ end
29
+ @length = 60 # minutes
30
+ class << self
31
+ attr_reader :notchcache, :length
32
+ def find v, mins=length, filter=nil, time=now
33
+ raise ArgumentError, "value cannot be nil" if v.nil?
34
+ select(mins, filter, time) do |notch|
35
+ notch.member?(v)
36
+ end
37
+ end
38
+ def range rng, filter=nil, time=now, &blk
39
+ rng.to_a.each { |idx|
40
+ notch = Bluth::TimingBelt.notch idx, filter, time
41
+ blk.call notch
42
+ }
43
+ end
44
+ # mins: the number of minutes to look ahead.
45
+ def each mins=length, filter=nil, time=now, &blk
46
+ mins.times { |idx|
47
+ notch = Bluth::TimingBelt.notch idx, filter, time
48
+ blk.call notch
49
+ }
50
+ end
51
+ def select mins=length, filter=nil, time=now, &blk
52
+ ret = []
53
+ each(mins, filter, time) { |notch| ret << notch if blk.call(notch) }
54
+ ret
55
+ end
56
+ def collect mins=length, filter=nil, time=now, &blk
57
+ ret = []
58
+ each(mins, filter, time) { |notch| ret << blk.call(notch) }
59
+ ret
60
+ end
61
+ def now mins=0, time=Time.now.utc
62
+ time + (mins*60) # time wants it in seconds
63
+ end
64
+ def stamp mins=0, time=now
65
+ (time + (mins*60)).strftime('%H:%M')
66
+ end
67
+ def notch mins=0, filter=nil, time=now
68
+ key = rediskey(stamp(mins, time), filter)
69
+ @notchcache ||= {}
70
+ if @notchcache[key].nil?
71
+ @notchcache[key] ||= Familia::Set.new key,
72
+ :ttl => 2*60*60, # 2 hours
73
+ :extend => Bluth::TimingBelt::Notch,
74
+ :db => Bluth::TimingBelt.db
75
+ @notchcache[key].stamp = stamp(mins, time)
76
+ @notchcache[key].filter = filter
77
+ @notchcache[key].time = now(mins, time)
78
+ end
79
+ @notchcache[key]
80
+ end
81
+ def priority minutes=2, filter=nil, time=now
82
+ (0..minutes).to_a.reverse.collect { |min| notch(min*-1, filter, time) }
83
+ end
84
+ def next_empty_notch filter=nil, time=now
85
+ length.times { |min|
86
+ possible = notch min+1, filter, time # add 1 so we don't start at 0
87
+ return possible if possible.empty?
88
+ }
89
+ nil
90
+ end
91
+ def add data, notch=nil
92
+ notch ||= Bluth::TimingBelt.notch 1
93
+ notch.add data
94
+ end
95
+ def pop minutes=2, filter=nil, time=now
96
+ gob = nil
97
+ priority = Bluth::TimingBelt.priority minutes, filter, time
98
+ begin
99
+ gobid, notch = nil, nil
100
+ priority.each { |n| gobid, notch = n.pop, n.name; break unless gobid.nil? }
101
+ unless gobid.nil?
102
+ Familia.ld "FOUND #{gobid} id #{notch}" if Familia.debug?
103
+ gob = Bluth::Gob.from_redis gobid
104
+ raise Bluth::Buster, "No such gob object: #{gobid}" if gob.nil?
105
+ Bluth::Queue.running << gob.jobid
106
+ gob.current_queue = :running
107
+ gob.save
108
+ end
109
+ rescue => ex
110
+ Familia.info ex.message
111
+ Familia.ld ex.backtrace if Familia.debug?
112
+ end
113
+ gob
114
+ end
115
+ end
116
+
117
+ end
118
+ end
data/lib/bluth/worker.rb CHANGED
@@ -200,7 +200,7 @@ module Bluth
200
200
 
201
201
  private
202
202
 
203
- # DO NOT return from this method
203
+ # DO NOT call return from this method
204
204
  def find_gob(task=nil)
205
205
  begin
206
206
  job = Bluth.pop
data/try/15_queue_try.rb CHANGED
@@ -27,4 +27,20 @@ Bluth::Queue.critical.size
27
27
  job = Bluth::Queue.critical.shift
28
28
  #=> 'job1'
29
29
 
30
+
31
+ ## Can create a queue on the fly
32
+ q = Bluth::Queue.create_queue :anything
33
+ q.rediskey
34
+ #=> "bluth:queue:anything"
35
+
36
+ ## And that new queue has a method
37
+ q = Bluth::Queue.queue :anything
38
+ q.class
39
+ #=> Familia::List
40
+
41
+ ## We can get a list of queues by priority
42
+ Bluth::Queue.entry_queues.collect { |q| q.name }
43
+ #=> [:critical, :high, :low]
44
+
45
+
30
46
  Bluth::Queue.critical.clear
data/try/17_gob_try.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'bluth'
2
2
  require 'bluth/test_helpers'
3
3
 
4
- Familia.debug = true
4
+ #Familia.debug = true
5
5
 
6
6
  ## Can enqueue a job
7
7
  @job = ExampleHandler.enqueue :arg1 => :val1
data/try/19_bluth_try.rb CHANGED
@@ -35,6 +35,7 @@ Bluth.queuetimeout = 2
35
35
  Bluth.pop
36
36
  #=> nil
37
37
 
38
+
38
39
  Bluth::Queue.critical.clear
39
40
  @job1.destroy! if @job1
40
41
  @job2.destroy! if @job2
@@ -0,0 +1,120 @@
1
+ require 'bluth'
2
+ require 'bluth/timingbelt'
3
+ require 'bluth/test_helpers'
4
+
5
+ #Familia.debug = true
6
+
7
+ @now = Time.at(1297641600).utc # 2011-02-14 20:00:00
8
+ Bluth::TimingBelt.redis.flushdb
9
+
10
+ ## Knows now
11
+ Bluth::TimingBelt.now(0, @now).to_s
12
+ #=> '2011-02-14 00:00:00 UTC'
13
+
14
+ ## Now can have an offset
15
+ Bluth::TimingBelt.now(5, @now).to_s
16
+ #=> '2011-02-14 00:05:00 UTC'
17
+
18
+ ## Can create a timestamp
19
+ Bluth::TimingBelt.stamp 0, @now
20
+ #=> '00:00'
21
+
22
+ ## Knows the current key
23
+ Bluth::TimingBelt.rediskey '00:00', nil
24
+ #=> 'bluth:timingbelt:00:00'
25
+
26
+ ## Creates a Set object for the current time
27
+ Bluth::TimingBelt.notch(0, nil, @now).class
28
+ #=> Familia::Set
29
+
30
+ ## A notch knows its stamp
31
+ Bluth::TimingBelt.notch(0, nil, @now).stamp
32
+ #=> '00:00'
33
+
34
+ ## A notch knows the next stamp
35
+ Bluth::TimingBelt.notch(0, nil, @now).next.stamp
36
+ #=> '00:01'
37
+
38
+ ## A notch knows the previous stamp
39
+ Bluth::TimingBelt.notch(0, nil, @now).prev.stamp
40
+ #=> '23:59'
41
+
42
+ ## A notch can skip to arbitrary number ahead
43
+ Bluth::TimingBelt.notch(0, nil, @now).skip(15).stamp
44
+ #=> '00:15'
45
+
46
+ ## Set for the current time doesn't exist
47
+ Bluth::TimingBelt.notch(0, nil, @now).exists?
48
+ #=> false
49
+
50
+ ## Set for the current time is empty
51
+ Bluth::TimingBelt.notch(0, nil, @now).empty?
52
+ #=> true
53
+
54
+ ## Knows the current set priority
55
+ Bluth::TimingBelt.priority(2, nil, @now).collect { |q| q.name }
56
+ #=> ["bluth:timingbelt:23:58", "bluth:timingbelt:23:59", "bluth:timingbelt:00:00"]
57
+
58
+ ## Handler can engauge right now
59
+ notch = Bluth::TimingBelt.notch(0, nil, @now)
60
+ ExampleHandler.engauge({}, notch).notch
61
+ #=> 'bluth:timingbelt:00:00'
62
+
63
+ ## Handler can engauge 1 minute ago
64
+ notch = Bluth::TimingBelt.notch(-1, nil, @now)
65
+ ExampleHandler.engauge({}, notch).notch
66
+ #=> 'bluth:timingbelt:23:59'
67
+
68
+ ## Handler can engauge 10 minutes from now
69
+ notch = Bluth::TimingBelt.notch(10, nil, @now)
70
+ @gob3 = ExampleHandler.engauge({}, notch)
71
+ @gob3.notch
72
+ #=> 'bluth:timingbelt:00:10'
73
+
74
+ ## Will get a job from the highest priority notch
75
+ @gob1 = Bluth::TimingBelt.pop(2, nil, @now)
76
+ @gob1.notch
77
+ #=> 'bluth:timingbelt:23:59'
78
+
79
+ ## Will get a job from the next priority notch
80
+ @gob2 = Bluth::TimingBelt.pop(2, nil, @now)
81
+ @gob2.notch
82
+ #=> 'bluth:timingbelt:00:00'
83
+
84
+ ## Knows next available notch
85
+ @next_notch = Bluth::TimingBelt.next_empty_notch(nil, @now)
86
+ @next_notch.name unless @next_notch.nil?
87
+ #=> 'bluth:timingbelt:00:01'
88
+
89
+ ## Knows next available notch
90
+ notches = Bluth::TimingBelt.find(@gob3.jobid, 60, nil, @now)
91
+ notches.first.name unless notches.first.nil?
92
+ #=> 'bluth:timingbelt:00:10'
93
+
94
+ ## Can calculate the difference between two notches
95
+ notch1 = Bluth::TimingBelt.notch
96
+ notch2 = Bluth::TimingBelt.notch 67
97
+ puts notch2.name
98
+ notch2 - notch1
99
+ #=> 67
100
+
101
+ ## A notch has an associated queue
102
+ notch = Bluth::TimingBelt.notch(0, nil, @now)
103
+ notch.queue.class
104
+ #=> Familia::List
105
+
106
+ ## And that queue has the same timestamp
107
+ notch = Bluth::TimingBelt.notch(0, nil, @now)
108
+ notch.queue.rediskey
109
+ #=> 'bluth:queue:00:00'
110
+
111
+ ## We can get a list of queues by priority
112
+ @current_notch = Bluth::TimingBelt.notch
113
+ Bluth::Queue.entry_queues.collect { |q| q.name }
114
+ #=> [:critical, @current_notch.prev.prev.queue.name, @current_notch.prev.queue.name, @current_notch.queue.name, :high, :low]
115
+
116
+ ## Just a test
117
+ Bluth.pop
118
+ ##=> true
119
+
120
+
metadata CHANGED
@@ -1,13 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bluth
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 6
9
- - 8
10
- version: 0.6.8
4
+ prerelease:
5
+ version: 0.7.0
11
6
  platform: ruby
12
7
  authors:
13
8
  - Delano Mandelbaum
@@ -15,7 +10,7 @@ autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
12
 
18
- date: 2011-02-08 00:00:00 -05:00
13
+ date: 2011-03-04 00:00:00 -05:00
19
14
  default_executable: bluth
20
15
  dependencies:
21
16
  - !ruby/object:Gem::Dependency
@@ -26,11 +21,6 @@ dependencies:
26
21
  requirements:
27
22
  - - ">="
28
23
  - !ruby/object:Gem::Version
29
- hash: 13
30
- segments:
31
- - 0
32
- - 6
33
- - 5
34
24
  version: 0.6.5
35
25
  type: :runtime
36
26
  version_requirements: *id001
@@ -42,11 +32,6 @@ dependencies:
42
32
  requirements:
43
33
  - - ">="
44
34
  - !ruby/object:Gem::Version
45
- hash: 5
46
- segments:
47
- - 0
48
- - 7
49
- - 3
50
35
  version: 0.7.3
51
36
  type: :runtime
52
37
  version_requirements: *id002
@@ -58,9 +43,6 @@ dependencies:
58
43
  requirements:
59
44
  - - ">="
60
45
  - !ruby/object:Gem::Version
61
- hash: 3
62
- segments:
63
- - 0
64
46
  version: "0"
65
47
  type: :runtime
66
48
  version_requirements: *id003
@@ -84,6 +66,7 @@ files:
84
66
  - lib/bluth.rb
85
67
  - lib/bluth/cli.rb
86
68
  - lib/bluth/test_helpers.rb
69
+ - lib/bluth/timingbelt.rb
87
70
  - lib/bluth/worker.rb
88
71
  - lib/daemonizing.rb
89
72
  - try/15_queue_try.rb
@@ -91,6 +74,7 @@ files:
91
74
  - try/17_gob_try.rb
92
75
  - try/18_handler_try.rb
93
76
  - try/19_bluth_try.rb
77
+ - try/30_timingbelt_try.rb
94
78
  has_rdoc: true
95
79
  homepage: http://github.com/delano/bluth
96
80
  licenses: []
@@ -105,23 +89,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
105
89
  requirements:
106
90
  - - ">="
107
91
  - !ruby/object:Gem::Version
108
- hash: 3
109
- segments:
110
- - 0
111
92
  version: "0"
112
93
  required_rubygems_version: !ruby/object:Gem::Requirement
113
94
  none: false
114
95
  requirements:
115
96
  - - ">="
116
97
  - !ruby/object:Gem::Version
117
- hash: 3
118
- segments:
119
- - 0
120
98
  version: "0"
121
99
  requirements: []
122
100
 
123
101
  rubyforge_project: bluth
124
- rubygems_version: 1.3.7
102
+ rubygems_version: 1.5.2
125
103
  signing_key:
126
104
  specification_version: 3
127
105
  summary: A Redis queuing system built on top of Familia (w/ daemons!)