resque 1.20.0 → 1.21.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of resque might be problematic. Click here for more details.
- data/HISTORY.md +10 -0
- data/Rakefile +5 -13
- data/lib/resque.rb +8 -17
- data/lib/resque/failure/redis.rb +1 -1
- data/lib/resque/helpers.rb +15 -4
- data/lib/resque/job.rb +9 -5
- data/lib/resque/vendor/utf8_util.rb +26 -0
- data/lib/resque/vendor/utf8_util/utf8_util_18.rb +91 -0
- data/lib/resque/vendor/utf8_util/utf8_util_19.rb +5 -0
- data/lib/resque/version.rb +1 -1
- data/lib/resque/worker.rb +3 -0
- data/test/resque_failure_redis_test.rb +23 -0
- data/test/test_helper.rb +9 -7
- data/test/worker_test.rb +45 -10
- metadata +11 -8
data/HISTORY.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
## 1.21.0 (2012-07-02)
|
2
|
+
|
3
|
+
* Add a flag to make sure failure hooks are only ran once (jakemack, #546)
|
4
|
+
* Support updated MultiJSON API (@twinturbo)
|
5
|
+
* Fix worker logging in monit example config (@twinturbo)
|
6
|
+
* loosen dependency of redis-namespace to 1.x, support for redis-rb 3.0.x
|
7
|
+
* change '%' to '$' in the 'stop program' command for monit
|
8
|
+
* UTF8 sanitize exception messages when there's a failure (@brianmario, #507)
|
9
|
+
* don't share a redis connection between parent and child (@jsanders, #588)
|
10
|
+
|
1
11
|
## 1.20.0 (2012-02-17)
|
2
12
|
|
3
13
|
* Fixed demos for ruby 1.9 (@BMorearty, #445)
|
data/Rakefile
CHANGED
@@ -20,18 +20,11 @@ require 'rake/testtask'
|
|
20
20
|
|
21
21
|
task :default => :test
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
end
|
30
|
-
else
|
31
|
-
Rake::TestTask.new do |test|
|
32
|
-
test.libs << "test"
|
33
|
-
test.test_files = FileList['test/**/*_test.rb']
|
34
|
-
end
|
23
|
+
Rake::TestTask.new do |test|
|
24
|
+
test.verbose = true
|
25
|
+
test.libs << "test"
|
26
|
+
test.libs << "lib"
|
27
|
+
test.test_files = FileList['test/**/*_test.rb']
|
35
28
|
end
|
36
29
|
|
37
30
|
if command? :kicker
|
@@ -74,5 +67,4 @@ task :publish do
|
|
74
67
|
sh "git push origin v#{Resque::Version}"
|
75
68
|
sh "git push origin master"
|
76
69
|
sh "git clean -fd"
|
77
|
-
exec "rake pages"
|
78
70
|
end
|
data/lib/resque.rb
CHANGED
@@ -13,6 +13,8 @@ require 'resque/job'
|
|
13
13
|
require 'resque/worker'
|
14
14
|
require 'resque/plugin'
|
15
15
|
|
16
|
+
require 'resque/vendor/utf8_util'
|
17
|
+
|
16
18
|
module Resque
|
17
19
|
include Helpers
|
18
20
|
extend self
|
@@ -77,9 +79,7 @@ module Resque
|
|
77
79
|
|
78
80
|
# Set a proc that will be called in the parent process before the
|
79
81
|
# worker forks for the first time.
|
80
|
-
|
81
|
-
@before_first_fork = before_first_fork
|
82
|
-
end
|
82
|
+
attr_writer :before_first_fork
|
83
83
|
|
84
84
|
# The `before_fork` hook will be run in the **parent** process
|
85
85
|
# before every job, so be careful- any changes you make will be
|
@@ -92,9 +92,7 @@ module Resque
|
|
92
92
|
end
|
93
93
|
|
94
94
|
# Set the before_fork proc.
|
95
|
-
|
96
|
-
@before_fork = before_fork
|
97
|
-
end
|
95
|
+
attr_writer :before_fork
|
98
96
|
|
99
97
|
# The `after_fork` hook will be run in the child process and is passed
|
100
98
|
# the current job. Any changes you make, therefore, will only live as
|
@@ -107,25 +105,18 @@ module Resque
|
|
107
105
|
end
|
108
106
|
|
109
107
|
# Set the after_fork proc.
|
110
|
-
|
111
|
-
@after_fork = after_fork
|
112
|
-
end
|
108
|
+
attr_writer :after_fork
|
113
109
|
|
114
110
|
def to_s
|
115
111
|
"Resque Client connected to #{redis_id}"
|
116
112
|
end
|
117
113
|
|
114
|
+
attr_accessor :inline
|
115
|
+
|
118
116
|
# If 'inline' is true Resque will call #perform method inline
|
119
117
|
# without queuing it into Redis and without any Resque callbacks.
|
120
118
|
# The 'inline' is false Resque jobs will be put in queue regularly.
|
121
|
-
|
122
|
-
@inline
|
123
|
-
end
|
124
|
-
alias_method :inline, :inline?
|
125
|
-
|
126
|
-
def inline=(inline)
|
127
|
-
@inline = inline
|
128
|
-
end
|
119
|
+
alias :inline? :inline
|
129
120
|
|
130
121
|
#
|
131
122
|
# queue manipulation
|
data/lib/resque/failure/redis.rb
CHANGED
@@ -8,7 +8,7 @@ module Resque
|
|
8
8
|
:failed_at => Time.now.strftime("%Y/%m/%d %H:%M:%S %Z"),
|
9
9
|
:payload => payload,
|
10
10
|
:exception => exception.class.to_s,
|
11
|
-
:error => exception.to_s,
|
11
|
+
:error => UTF8Util.clean(exception.to_s),
|
12
12
|
:backtrace => filter_backtrace(Array(exception.backtrace)),
|
13
13
|
:worker => worker.to_s,
|
14
14
|
:queue => queue
|
data/lib/resque/helpers.rb
CHANGED
@@ -2,8 +2,10 @@ require 'multi_json'
|
|
2
2
|
|
3
3
|
# OkJson won't work because it doesn't serialize symbols
|
4
4
|
# in the same way yajl and json do.
|
5
|
-
if MultiJson.
|
6
|
-
raise "Please install the yajl-ruby or json gem"
|
5
|
+
if MultiJson.respond_to?(:adapter)
|
6
|
+
raise "Please install the yajl-ruby or json gem" if MultiJson.adapter.to_s == 'MultiJson::Adapters::OkJson'
|
7
|
+
elsif MultiJson.respond_to?(:engine)
|
8
|
+
raise "Please install the yajl-ruby or json gem" if MultiJson.engine.to_s == 'MultiJson::Engines::OkJson'
|
7
9
|
end
|
8
10
|
|
9
11
|
module Resque
|
@@ -19,7 +21,12 @@ module Resque
|
|
19
21
|
# Given a Ruby object, returns a string suitable for storage in a
|
20
22
|
# queue.
|
21
23
|
def encode(object)
|
22
|
-
|
24
|
+
if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
|
25
|
+
MultiJson.dump object
|
26
|
+
else
|
27
|
+
MultiJson.encode object
|
28
|
+
end
|
29
|
+
|
23
30
|
end
|
24
31
|
|
25
32
|
# Given a string, returns a Ruby object.
|
@@ -27,7 +34,11 @@ module Resque
|
|
27
34
|
return unless object
|
28
35
|
|
29
36
|
begin
|
30
|
-
|
37
|
+
if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
|
38
|
+
MultiJson.load object
|
39
|
+
else
|
40
|
+
MultiJson.decode object
|
41
|
+
end
|
31
42
|
rescue ::MultiJson::DecodeError => e
|
32
43
|
raise DecodeException, e.message, e.backtrace
|
33
44
|
end
|
data/lib/resque/job.rb
CHANGED
@@ -32,6 +32,7 @@ module Resque
|
|
32
32
|
def initialize(queue, payload)
|
33
33
|
@queue = queue
|
34
34
|
@payload = payload
|
35
|
+
@failure_hooks_ran = false
|
35
36
|
end
|
36
37
|
|
37
38
|
# Creates a job by placing it on a queue. Expects a string queue
|
@@ -210,14 +211,17 @@ module Resque
|
|
210
211
|
@after_hooks ||= Plugin.after_hooks(payload_class)
|
211
212
|
end
|
212
213
|
|
213
|
-
def failure_hooks
|
214
|
+
def failure_hooks
|
214
215
|
@failure_hooks ||= Plugin.failure_hooks(payload_class)
|
215
216
|
end
|
216
|
-
|
217
|
+
|
217
218
|
def run_failure_hooks(exception)
|
218
|
-
|
219
|
-
|
219
|
+
begin
|
220
|
+
job_args = args || []
|
221
|
+
failure_hooks.each { |hook| payload_class.send(hook, exception, *job_args) } unless @failure_hooks_ran
|
222
|
+
ensure
|
223
|
+
@failure_hooks_ran = true
|
224
|
+
end
|
220
225
|
end
|
221
|
-
|
222
226
|
end
|
223
227
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module UTF8Util
|
2
|
+
# use '?' intsead of the unicode replace char, since that is 3 bytes
|
3
|
+
# and can increase the string size if it's done a lot
|
4
|
+
REPLACEMENT_CHAR = "?"
|
5
|
+
|
6
|
+
# Replace invalid UTF-8 character sequences with a replacement character
|
7
|
+
#
|
8
|
+
# Returns self as valid UTF-8.
|
9
|
+
def self.clean!(str)
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
# Replace invalid UTF-8 character sequences with a replacement character
|
14
|
+
#
|
15
|
+
# Returns a copy of this String as valid UTF-8.
|
16
|
+
def self.clean(str)
|
17
|
+
clean!(str.dup)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
if RUBY_VERSION <= '1.9'
|
23
|
+
require 'resque/vendor/utf8_util/utf8_util_18'
|
24
|
+
else
|
25
|
+
require 'resque/vendor/utf8_util/utf8_util_19'
|
26
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module UTF8Util
|
4
|
+
HIGH_BIT_RANGE = /[\x80-\xff]/
|
5
|
+
|
6
|
+
# Check if this String is valid UTF-8
|
7
|
+
#
|
8
|
+
# Returns true or false.
|
9
|
+
def self.valid?(str)
|
10
|
+
sc = StringScanner.new(str)
|
11
|
+
|
12
|
+
while sc.skip_until(HIGH_BIT_RANGE)
|
13
|
+
sc.pos -= 1
|
14
|
+
|
15
|
+
if !sequence_length(sc)
|
16
|
+
return false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
# Replace invalid UTF-8 character sequences with a replacement character
|
24
|
+
#
|
25
|
+
# Returns self as valid UTF-8.
|
26
|
+
def self.clean!(str)
|
27
|
+
sc = StringScanner.new(str)
|
28
|
+
while sc.skip_until(HIGH_BIT_RANGE)
|
29
|
+
pos = sc.pos = sc.pos-1
|
30
|
+
|
31
|
+
if !sequence_length(sc)
|
32
|
+
str[pos] = REPLACEMENT_CHAR
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
str
|
37
|
+
end
|
38
|
+
|
39
|
+
# Validate the UTF-8 sequence at the current scanner position.
|
40
|
+
#
|
41
|
+
# scanner - StringScanner instance so we can advance the pointer as we verify.
|
42
|
+
#
|
43
|
+
# Returns The length in bytes of this UTF-8 sequence, false if invalid.
|
44
|
+
def self.sequence_length(scanner)
|
45
|
+
leader = scanner.get_byte[0]
|
46
|
+
|
47
|
+
if (leader >> 5) == 0x6
|
48
|
+
if check_next_sequence(scanner)
|
49
|
+
return 2
|
50
|
+
else
|
51
|
+
scanner.pos -= 1
|
52
|
+
end
|
53
|
+
elsif (leader >> 4) == 0x0e
|
54
|
+
if check_next_sequence(scanner)
|
55
|
+
if check_next_sequence(scanner)
|
56
|
+
return 3
|
57
|
+
else
|
58
|
+
scanner.pos -= 2
|
59
|
+
end
|
60
|
+
else
|
61
|
+
scanner.pos -= 1
|
62
|
+
end
|
63
|
+
elsif (leader >> 3) == 0x1e
|
64
|
+
if check_next_sequence(scanner)
|
65
|
+
if check_next_sequence(scanner)
|
66
|
+
if check_next_sequence(scanner)
|
67
|
+
return 4
|
68
|
+
else
|
69
|
+
scanner.pos -= 3
|
70
|
+
end
|
71
|
+
else
|
72
|
+
scanner.pos -= 2
|
73
|
+
end
|
74
|
+
else
|
75
|
+
scanner.pos -= 1
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Read another byte off the scanner oving the scan position forward one place
|
85
|
+
#
|
86
|
+
# Returns nothing.
|
87
|
+
def self.check_next_sequence(scanner)
|
88
|
+
byte = scanner.get_byte[0]
|
89
|
+
(byte >> 6) == 0x2
|
90
|
+
end
|
91
|
+
end
|
data/lib/resque/version.rb
CHANGED
data/lib/resque/worker.rb
CHANGED
@@ -89,6 +89,8 @@ module Resque
|
|
89
89
|
# removed without needing to restart workers using this method.
|
90
90
|
def initialize(*queues)
|
91
91
|
@queues = queues.map { |queue| queue.to_s.strip }
|
92
|
+
@shutdown = nil
|
93
|
+
@paused = nil
|
92
94
|
validate_queues
|
93
95
|
end
|
94
96
|
|
@@ -138,6 +140,7 @@ module Resque
|
|
138
140
|
Process.wait(@child)
|
139
141
|
else
|
140
142
|
procline "Processing #{job.queue} since #{Time.now.to_i}"
|
143
|
+
redis.client.reconnect # Don't share connection with parent
|
141
144
|
perform(job, &block)
|
142
145
|
exit! unless @cant_fork
|
143
146
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'resque/failure/redis'
|
3
|
+
|
4
|
+
context "Resque::Failure::Redis" do
|
5
|
+
setup do
|
6
|
+
@bad_string = [39, 250, 141, 168, 138, 191, 52, 211, 159, 86, 93, 95, 39].map { |c| c.chr }.join
|
7
|
+
exception = StandardError.exception(@bad_string)
|
8
|
+
worker = Resque::Worker.new(:test)
|
9
|
+
queue = "queue"
|
10
|
+
payload = { "class" => Object, "args" => 3 }
|
11
|
+
@redis_backend = Resque::Failure::Redis.new(exception, worker, queue, payload)
|
12
|
+
end
|
13
|
+
|
14
|
+
test 'cleans up bad strings before saving the failure, in order to prevent errors on the resque UI' do
|
15
|
+
# test assumption: the bad string should not be able to round trip though JSON
|
16
|
+
assert_raises(MultiJson::DecodeError) {
|
17
|
+
MultiJson.decode(MultiJson.encode(@bad_string))
|
18
|
+
}
|
19
|
+
|
20
|
+
@redis_backend.save
|
21
|
+
Resque::Failure::Redis.all # should not raise an error
|
22
|
+
end
|
23
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'bundler'
|
3
|
-
Bundler.setup(:default, :test)
|
4
|
-
Bundler.require(:default, :test)
|
5
2
|
|
6
3
|
dir = File.dirname(File.expand_path(__FILE__))
|
7
4
|
$LOAD_PATH.unshift dir + '/../lib'
|
8
5
|
$TESTING = true
|
9
6
|
require 'test/unit'
|
10
7
|
|
8
|
+
require 'redis/namespace'
|
9
|
+
require 'resque'
|
10
|
+
|
11
11
|
begin
|
12
12
|
require 'leftright'
|
13
13
|
rescue LoadError
|
@@ -147,12 +147,14 @@ end
|
|
147
147
|
class Time
|
148
148
|
# Thanks, Timecop
|
149
149
|
class << self
|
150
|
+
attr_accessor :fake_time
|
151
|
+
|
150
152
|
alias_method :now_without_mock_time, :now
|
151
153
|
|
152
|
-
def
|
153
|
-
|
154
|
+
def now
|
155
|
+
fake_time || now_without_mock_time
|
154
156
|
end
|
155
|
-
|
156
|
-
alias_method :now, :now_with_mock_time
|
157
157
|
end
|
158
|
+
|
159
|
+
self.fake_time = nil
|
158
160
|
end
|
data/test/worker_test.rb
CHANGED
@@ -43,7 +43,7 @@ context "Resque::Worker" do
|
|
43
43
|
def self.on_failure_record_failure(exception, *job_args)
|
44
44
|
@@exception = exception
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
def self.exception
|
48
48
|
@@exception
|
49
49
|
end
|
@@ -57,6 +57,29 @@ context "Resque::Worker" do
|
|
57
57
|
assert(SimpleJobWithFailureHandling.exception.kind_of?(Resque::DirtyExit))
|
58
58
|
end
|
59
59
|
|
60
|
+
class ::SimpleFailingJob
|
61
|
+
@@exception_count = 0
|
62
|
+
|
63
|
+
def self.on_failure_record_failure(exception, *job_args)
|
64
|
+
@@exception_count += 1
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.exception_count
|
68
|
+
@@exception_count
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.perform
|
72
|
+
raise Exception.new
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
test "only calls failure hook once on exception" do
|
77
|
+
job = Resque::Job.new(:jobs, {'class' => 'SimpleFailingJob', 'args' => ""})
|
78
|
+
@worker.perform(job)
|
79
|
+
assert_equal 1, Resque::Failure.count
|
80
|
+
assert_equal 1, SimpleFailingJob.exception_count
|
81
|
+
end
|
82
|
+
|
60
83
|
test "can peek at failed jobs" do
|
61
84
|
10.times { Resque::Job.create(:jobs, BadJob) }
|
62
85
|
@worker.work(0)
|
@@ -355,16 +378,22 @@ context "Resque::Worker" do
|
|
355
378
|
end
|
356
379
|
|
357
380
|
test "very verbose works in the afternoon" do
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
singleton.send :define_method, :puts, lambda { |thing| $last_puts = thing }
|
381
|
+
begin
|
382
|
+
require 'time'
|
383
|
+
last_puts = ""
|
384
|
+
Time.fake_time = Time.parse("15:44:33 2011-03-02")
|
363
385
|
|
364
|
-
|
365
|
-
|
386
|
+
@worker.extend(Module.new {
|
387
|
+
define_method(:puts) { |thing| last_puts = thing }
|
388
|
+
})
|
366
389
|
|
367
|
-
|
390
|
+
@worker.very_verbose = true
|
391
|
+
@worker.log("some log text")
|
392
|
+
|
393
|
+
assert_match /\*\* \[15:44:33 2011-03-02\] \d+: some log text/, last_puts
|
394
|
+
ensure
|
395
|
+
Time.fake_time = nil
|
396
|
+
end
|
368
397
|
end
|
369
398
|
|
370
399
|
test "Will call an after_fork hook after forking" do
|
@@ -382,7 +411,7 @@ context "Resque::Worker" do
|
|
382
411
|
test "returns PID of running process" do
|
383
412
|
assert_equal @worker.to_s.split(":")[1].to_i, @worker.pid
|
384
413
|
end
|
385
|
-
|
414
|
+
|
386
415
|
test "requeue failed queue" do
|
387
416
|
queue = 'good_job'
|
388
417
|
Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
|
@@ -402,4 +431,10 @@ context "Resque::Worker" do
|
|
402
431
|
assert_equal queue2, Resque::Failure.all(0)['queue']
|
403
432
|
assert_equal 1, Resque::Failure.count
|
404
433
|
end
|
434
|
+
|
435
|
+
test "reconnects to redis after fork" do
|
436
|
+
original_connection = Resque.redis.client.connection.instance_variable_get("@sock")
|
437
|
+
@worker.work(0)
|
438
|
+
assert_not_equal original_connection, Resque.redis.client.connection.instance_variable_get("@sock")
|
439
|
+
end
|
405
440
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resque
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 67
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
8
|
+
- 21
|
9
9
|
- 0
|
10
|
-
version: 1.
|
10
|
+
version: 1.21.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Chris Wanstrath
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2012-
|
19
|
+
date: 2012-07-03 00:00:00 Z
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
22
|
name: redis-namespace
|
@@ -26,12 +26,11 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
29
|
+
hash: 15
|
30
30
|
segments:
|
31
31
|
- 1
|
32
32
|
- 0
|
33
|
-
|
34
|
-
version: 1.0.2
|
33
|
+
version: "1.0"
|
35
34
|
type: :runtime
|
36
35
|
version_requirements: *id001
|
37
36
|
- !ruby/object:Gem::Dependency
|
@@ -127,6 +126,9 @@ files:
|
|
127
126
|
- lib/resque/plugin.rb
|
128
127
|
- lib/resque/server.rb
|
129
128
|
- lib/resque/helpers.rb
|
129
|
+
- lib/resque/vendor/utf8_util.rb
|
130
|
+
- lib/resque/vendor/utf8_util/utf8_util_19.rb
|
131
|
+
- lib/resque/vendor/utf8_util/utf8_util_18.rb
|
130
132
|
- lib/resque/tasks.rb
|
131
133
|
- lib/resque/stat.rb
|
132
134
|
- lib/resque/failure/hoptoad.rb
|
@@ -141,6 +143,7 @@ files:
|
|
141
143
|
- test/airbrake_test.rb
|
142
144
|
- test/job_hooks_test.rb
|
143
145
|
- test/plugin_test.rb
|
146
|
+
- test/resque_failure_redis_test.rb
|
144
147
|
- test/redis-test-cluster.conf
|
145
148
|
- test/resque-web_test.rb
|
146
149
|
- test/redis-test.conf
|
@@ -178,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
178
181
|
requirements: []
|
179
182
|
|
180
183
|
rubyforge_project:
|
181
|
-
rubygems_version: 1.8.
|
184
|
+
rubygems_version: 1.8.21
|
182
185
|
signing_key:
|
183
186
|
specification_version: 3
|
184
187
|
summary: Resque is a Redis-backed queueing system.
|