quebert 1.0.9 → 1.11.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +0 -1
- data/Guardfile +1 -4
- data/lib/quebert/backend/beanstalk.rb +27 -15
- data/lib/quebert/command_line_runner.rb +16 -14
- data/lib/quebert/controller/beanstalk.rb +3 -3
- data/lib/quebert/job.rb +17 -21
- data/lib/quebert/version.rb +2 -2
- data/lib/quebert/worker.rb +19 -10
- data/quebert.gemspec +1 -1
- data/spec/configuration_spec.rb +4 -2
- data/spec/consumer_spec.rb +20 -20
- data/spec/worker_spec.rb +6 -7
- metadata +8 -6
data/Gemfile
CHANGED
data/Guardfile
CHANGED
@@ -1,7 +1,4 @@
|
|
1
|
-
|
2
|
-
# More info at https://github.com/guard/guard#readme
|
3
|
-
|
4
|
-
guard 'rspec', :version => 1, :cli => '--colour --format nested' do
|
1
|
+
guard 'rspec', :version => 2, :cli => '--colour --format nested' do
|
5
2
|
watch(%r{^spec/.+_spec\.rb$})
|
6
3
|
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
4
|
watch('spec/spec_helper.rb') { "spec" }
|
@@ -1,36 +1,48 @@
|
|
1
|
-
require '
|
1
|
+
require 'beaneater'
|
2
2
|
|
3
3
|
module Quebert
|
4
4
|
module Backend
|
5
5
|
|
6
6
|
# Manage jobs on a Beanstalk queue out of process
|
7
|
-
class Beanstalk
|
7
|
+
class Beanstalk
|
8
8
|
def put(job, *args)
|
9
|
-
|
9
|
+
priority, delay, ttr = args
|
10
|
+
opts = {}
|
11
|
+
opts[:pri] = priority unless priority.nil?
|
12
|
+
opts[:delay] = delay unless delay.nil?
|
13
|
+
opts[:ttr] = ttr unless ttr.nil?
|
14
|
+
@tube.put job.to_json, opts
|
10
15
|
end
|
11
|
-
|
12
|
-
def
|
13
|
-
|
16
|
+
|
17
|
+
def reserve_without_controller(timeout=nil)
|
18
|
+
@tube.reserve timeout
|
14
19
|
end
|
15
|
-
|
16
|
-
|
17
|
-
|
20
|
+
|
21
|
+
def reserve(timeout=nil)
|
22
|
+
Controller::Beanstalk.new reserve_without_controller(timeout), self
|
23
|
+
end
|
24
|
+
|
25
|
+
def peek(state)
|
26
|
+
@tube.peek state
|
27
|
+
end
|
28
|
+
|
18
29
|
# For testing purposes... I think there's a better way to do this though.
|
19
30
|
def drain!
|
20
|
-
while
|
31
|
+
while peek(:ready) do
|
21
32
|
reserve_without_controller.delete
|
22
33
|
end
|
23
|
-
while
|
34
|
+
while peek(:delayed) do
|
24
35
|
reserve_without_controller.delete
|
25
36
|
end
|
26
|
-
while
|
27
|
-
|
37
|
+
while peek(:buried) do
|
38
|
+
@tube.kick
|
28
39
|
reserve_without_controller.delete
|
29
40
|
end
|
30
41
|
end
|
31
42
|
|
32
|
-
def initialize(host,
|
33
|
-
|
43
|
+
def initialize(host, tube)
|
44
|
+
@pool = Beaneater::Pool.new Array(host)
|
45
|
+
@tube = @pool.tubes[tube]
|
34
46
|
end
|
35
47
|
def self.configure(opts={})
|
36
48
|
opts[:host] ||= ['127.0.0.1:11300']
|
@@ -1,45 +1,47 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
1
3
|
module Quebert
|
2
4
|
class CommandLineRunner
|
3
|
-
|
5
|
+
|
4
6
|
attr_accessor :arguments, :command, :options
|
5
|
-
|
7
|
+
|
6
8
|
def initialize(argv)
|
7
9
|
@argv = argv
|
8
|
-
|
10
|
+
|
9
11
|
# Default options values
|
10
12
|
@options = {
|
11
13
|
:chdir => Dir.pwd
|
12
14
|
}
|
13
|
-
|
15
|
+
|
14
16
|
parse!
|
15
17
|
end
|
16
|
-
|
18
|
+
|
17
19
|
def parser
|
18
20
|
@parser ||= OptionParser.new do |opts|
|
19
21
|
opts.banner = "Usage: quebert [options]"
|
20
|
-
|
22
|
+
|
21
23
|
opts.separator ""
|
22
|
-
|
23
|
-
opts.on("-l", "--log FILE", "File to redirect output " +
|
24
|
+
|
25
|
+
opts.on("-l", "--log FILE", "File to redirect output " +
|
24
26
|
"(default: #{@options[:log]})") { |file| @options[:log] = file }
|
25
|
-
opts.on("-P", "--pid FILE", "File to store PID " +
|
27
|
+
opts.on("-P", "--pid FILE", "File to store PID " +
|
26
28
|
"(default: #{@options[:pid]})") { |file| @options[:pid] = file }
|
27
29
|
opts.on("-C", "--config FILE", "Load options from config file") { |file| @options[:config] = file }
|
28
30
|
opts.on("-c", "--chdir DIR", "Change to dir before starting") { |dir| @options[:chdir] = File.expand_path(dir) }
|
29
31
|
end
|
30
32
|
end
|
31
|
-
|
33
|
+
|
32
34
|
# Parse the options.
|
33
35
|
def parse!
|
34
36
|
parser.parse! @argv
|
35
37
|
@command = @argv.shift
|
36
38
|
@arguments = @argv
|
37
39
|
end
|
38
|
-
|
40
|
+
|
39
41
|
def self.dispatch(args = ARGV)
|
40
42
|
runner = new(args)
|
41
43
|
params = runner.options
|
42
|
-
|
44
|
+
|
43
45
|
if dir = params[:chdir]
|
44
46
|
Dir.chdir dir
|
45
47
|
end
|
@@ -55,10 +57,10 @@ module Quebert
|
|
55
57
|
if config = params[:config] || auto_config
|
56
58
|
require config
|
57
59
|
end
|
58
|
-
|
60
|
+
|
59
61
|
Worker.new.start
|
60
62
|
end
|
61
|
-
|
63
|
+
|
62
64
|
private
|
63
65
|
def self.auto_config
|
64
66
|
rails_env_path = './config/environment.rb'
|
@@ -52,7 +52,7 @@ module Quebert
|
|
52
52
|
log "Job deleted", :error
|
53
53
|
rescue Job::Release
|
54
54
|
log "Releasing with priority: #{@job.priority} and delay: #{@job.delay}", :error
|
55
|
-
beanstalk_job.release @job.priority, @job.delay
|
55
|
+
beanstalk_job.release :pri => @job.priority, :delay => @job.delay
|
56
56
|
log "Job released", :error
|
57
57
|
rescue Job::Bury
|
58
58
|
log "Burrying job", :error
|
@@ -86,10 +86,10 @@ module Quebert
|
|
86
86
|
log "Job burried"
|
87
87
|
else
|
88
88
|
log "TTR exceeded. Releasing with priority: #{@job.priority} and delay: #{delay}"
|
89
|
-
beanstalk_job.release @job.priority, delay
|
89
|
+
beanstalk_job.release :pri => @job.priority, :delay => delay
|
90
90
|
log "Job released"
|
91
91
|
end
|
92
|
-
rescue ::
|
92
|
+
rescue ::Beaneater::NotFoundError => e
|
93
93
|
log "Job ran longer than allowed. Beanstalk already deleted it!!!!", :error
|
94
94
|
# Sometimes the timer doesn't behave correctly and this job actually runs longer than
|
95
95
|
# allowed. At that point the beanstalk job no longer exists anymore. Lets let it go and don't blow up.
|
data/lib/quebert/job.rb
CHANGED
@@ -6,20 +6,20 @@ module Quebert
|
|
6
6
|
|
7
7
|
attr_reader :args
|
8
8
|
attr_accessor :priority, :delay, :ttr
|
9
|
-
|
9
|
+
|
10
10
|
DEFAULT_JOB_PRIORITY = 65536
|
11
11
|
DEFAULT_JOB_DELAY = 0
|
12
12
|
DEFAULT_JOB_TTR = 10
|
13
13
|
|
14
|
-
# A buffer time in seconds added to the Beanstalk TTR for Quebert to do its own job cleanup
|
14
|
+
# A buffer time in seconds added to the Beanstalk TTR for Quebert to do its own job cleanup
|
15
15
|
# The job will perform based on the Beanstalk TTR, but Beanstalk hangs on to the job just a
|
16
16
|
# little longer so that Quebert can bury the job or schedule a retry with the appropriate delay
|
17
17
|
QUEBERT_TTR_BUFFER = 5
|
18
18
|
|
19
19
|
NotImplemented = Class.new(StandardError)
|
20
|
-
|
20
|
+
|
21
21
|
Action = Class.new(Exception)
|
22
|
-
|
22
|
+
|
23
23
|
Bury = Class.new(Action)
|
24
24
|
Delete = Class.new(Action)
|
25
25
|
Release = Class.new(Action)
|
@@ -28,7 +28,7 @@ module Quebert
|
|
28
28
|
|
29
29
|
def initialize(*args)
|
30
30
|
opts = args.last.is_a?(::Hash) ? args.pop : nil
|
31
|
-
|
31
|
+
|
32
32
|
@priority = DEFAULT_JOB_PRIORITY
|
33
33
|
@delay = DEFAULT_JOB_DELAY
|
34
34
|
@ttr = DEFAULT_JOB_TTR
|
@@ -36,7 +36,7 @@ module Quebert
|
|
36
36
|
if opts
|
37
37
|
beanstalk_opts = opts.delete(:beanstalk)
|
38
38
|
args << opts unless opts.empty?
|
39
|
-
|
39
|
+
|
40
40
|
if beanstalk_opts
|
41
41
|
@priority = beanstalk_opts[:priority] if beanstalk_opts[:priority]
|
42
42
|
@delay = beanstalk_opts[:delay] if beanstalk_opts[:delay]
|
@@ -46,41 +46,37 @@ module Quebert
|
|
46
46
|
|
47
47
|
@args = args.dup.freeze
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
def perform(*args)
|
51
51
|
raise NotImplemented
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
# Runs the perform method that somebody else should be implementing
|
55
55
|
def perform!
|
56
56
|
# Honor the timeout and kill the job in ruby-space. Beanstalk
|
57
57
|
# should be cleaning up this job and returning it to the queue
|
58
58
|
# as well.
|
59
|
-
|
60
|
-
Quebert::Timeout.timeout(@ttr){ perform(*args) }
|
61
|
-
rescue ::Timeout::Error => e
|
62
|
-
raise Job::Timeout, e.message, e.backtrace
|
63
|
-
end
|
59
|
+
Quebert::Timeout.timeout(@ttr, Job::Timeout){ perform(*args) }
|
64
60
|
end
|
65
|
-
|
61
|
+
|
66
62
|
def enqueue
|
67
63
|
self.class.backend.put self, @priority, @delay, @ttr + QUEBERT_TTR_BUFFER
|
68
64
|
end
|
69
|
-
|
65
|
+
|
70
66
|
def to_json
|
71
67
|
JSON.generate(Serializer::Job.serialize(self))
|
72
68
|
end
|
73
|
-
|
69
|
+
|
74
70
|
def self.from_json(json)
|
75
71
|
if hash = JSON.parse(json) and not hash.empty?
|
76
72
|
Serializer::Job.deserialize(hash)
|
77
73
|
end
|
78
74
|
end
|
79
|
-
|
75
|
+
|
80
76
|
def self.backend=(backend)
|
81
77
|
@backend = backend
|
82
78
|
end
|
83
|
-
|
79
|
+
|
84
80
|
def self.backend
|
85
81
|
@backend || Quebert.configuration.backend
|
86
82
|
end
|
@@ -89,13 +85,13 @@ module Quebert
|
|
89
85
|
def delete!
|
90
86
|
raise Delete
|
91
87
|
end
|
92
|
-
|
88
|
+
|
93
89
|
def release!
|
94
90
|
raise Release
|
95
91
|
end
|
96
|
-
|
92
|
+
|
97
93
|
def bury!
|
98
94
|
raise Bury
|
99
95
|
end
|
100
96
|
end
|
101
|
-
end
|
97
|
+
end
|
data/lib/quebert/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module Quebert
|
2
|
-
VERSION = "1.0
|
3
|
-
end
|
2
|
+
VERSION = "1.11.0"
|
3
|
+
end
|
data/lib/quebert/worker.rb
CHANGED
@@ -3,29 +3,38 @@ module Quebert
|
|
3
3
|
include Logging
|
4
4
|
|
5
5
|
attr_accessor :exception_handler, :backend
|
6
|
-
|
6
|
+
|
7
7
|
def initialize
|
8
8
|
yield self if block_given?
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
# Start the worker backend and intercept exceptions if a handler is provided
|
12
12
|
def start
|
13
13
|
Signal.trap('TERM') { safe_stop }
|
14
14
|
Signal.trap('INT') { safe_stop }
|
15
15
|
|
16
16
|
logger.info "Worker started with #{backend.class.name} backend\n"
|
17
|
-
while @controller = backend.reserve do
|
17
|
+
while @controller = backend.reserve do
|
18
18
|
begin
|
19
19
|
@controller.perform
|
20
|
-
rescue Exception =>
|
21
|
-
|
22
|
-
|
20
|
+
rescue Exception => error
|
21
|
+
if exception_handler
|
22
|
+
exception_handler.call(
|
23
|
+
error,
|
24
|
+
:controller => @controller,
|
25
|
+
:pid => $$,
|
26
|
+
:worker => self
|
27
|
+
)
|
28
|
+
else
|
29
|
+
raise error
|
30
|
+
end
|
31
|
+
end
|
23
32
|
@controller = nil
|
24
33
|
|
25
34
|
stop if @terminate_sent
|
26
35
|
end
|
27
36
|
end
|
28
|
-
|
37
|
+
|
29
38
|
def safe_stop
|
30
39
|
if @terminate_sent
|
31
40
|
logger.info "Ok! I get the point. Shutting down immediately."
|
@@ -41,14 +50,14 @@ module Quebert
|
|
41
50
|
logger.info "Worker stopping\n"
|
42
51
|
exit 0
|
43
52
|
end
|
44
|
-
|
53
|
+
|
45
54
|
protected
|
46
55
|
def backend
|
47
56
|
@backend ||= Quebert.config.backend
|
48
57
|
end
|
49
|
-
|
58
|
+
|
50
59
|
def exception_handler
|
51
60
|
@exception_handler ||= Quebert.config.worker.exception_handler
|
52
61
|
end
|
53
62
|
end
|
54
|
-
end
|
63
|
+
end
|
data/quebert.gemspec
CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
|
|
21
21
|
# specify any dependencies here; for example:
|
22
22
|
# s.add_development_dependency "rspec"
|
23
23
|
s.add_runtime_dependency "json"
|
24
|
-
s.add_runtime_dependency "
|
24
|
+
s.add_runtime_dependency "beaneater"
|
25
25
|
|
26
26
|
s.add_development_dependency 'rspec', '2.7.0'
|
27
27
|
end
|
data/spec/configuration_spec.rb
CHANGED
@@ -10,8 +10,10 @@ describe Configuration do
|
|
10
10
|
backend = @config.backend
|
11
11
|
backend.should be_instance_of(Quebert::Backend::Beanstalk)
|
12
12
|
# Blech, gross nastiness in their lib, but we need to look in to see if this stuff as configed
|
13
|
-
backend.instance_variable_get('@
|
14
|
-
|
13
|
+
pool = backend.instance_variable_get('@pool')
|
14
|
+
pool.connections.first.address.should eql('localhost:11300')
|
15
|
+
tube = backend.instance_variable_get('@tube')
|
16
|
+
tube.name.should eql('quebert-config-test')
|
15
17
|
end
|
16
18
|
|
17
19
|
end
|
data/spec/consumer_spec.rb
CHANGED
@@ -25,63 +25,63 @@ describe Controller::Beanstalk do
|
|
25
25
|
|
26
26
|
it "should delete job off queue after succesful run" do
|
27
27
|
@q.put Adder.new(1, 2)
|
28
|
-
@q.
|
28
|
+
@q.peek(:ready).should_not be_nil
|
29
29
|
@q.reserve.perform.should eql(3)
|
30
|
-
@q.
|
30
|
+
@q.peek(:ready).should be_nil
|
31
31
|
end
|
32
32
|
|
33
33
|
it "should bury job if an exception occurs in job" do
|
34
34
|
@q.put Exceptional.new
|
35
|
-
@q.
|
35
|
+
@q.peek(:ready).should_not be_nil
|
36
36
|
lambda{ @q.reserve.perform }.should raise_exception
|
37
|
-
@q.
|
37
|
+
@q.peek(:buried).should_not be_nil
|
38
38
|
end
|
39
39
|
|
40
40
|
it "should bury an AR job if an exception occurs deserializing it" do
|
41
41
|
@user = User.new(:first_name => "John", :last_name => "Doe", :email => "jdoe@gmail.com")
|
42
42
|
@user.id = 1
|
43
43
|
@q.put Serializer::ActiveRecord.serialize(@user)
|
44
|
-
@q.
|
44
|
+
@q.peek(:ready).should_not be_nil
|
45
45
|
lambda{ @q.reserve.perform }.should raise_exception
|
46
|
-
@q.
|
46
|
+
@q.peek(:buried).should_not be_nil
|
47
47
|
end
|
48
48
|
|
49
49
|
context "job actions" do
|
50
50
|
it "should delete job" do
|
51
51
|
@q.put DeleteJob.new
|
52
|
-
@q.
|
52
|
+
@q.peek(:ready).should_not be_nil
|
53
53
|
@q.reserve.perform
|
54
|
-
@q.
|
54
|
+
@q.peek(:ready).should be_nil
|
55
55
|
end
|
56
56
|
|
57
57
|
it "should release job" do
|
58
58
|
@q.put ReleaseJob.new
|
59
|
-
@q.
|
59
|
+
@q.peek(:ready).should_not be_nil
|
60
60
|
@q.reserve.perform
|
61
|
-
@q.
|
61
|
+
@q.peek(:ready).should_not be_nil
|
62
62
|
end
|
63
63
|
|
64
64
|
it "should bury job" do
|
65
65
|
@q.put BuryJob.new
|
66
|
-
@q.
|
67
|
-
@q.
|
66
|
+
@q.peek(:ready).should_not be_nil
|
67
|
+
@q.peek(:buried).should be_nil
|
68
68
|
@q.reserve.perform
|
69
|
-
@q.
|
70
|
-
@q.
|
69
|
+
@q.peek(:ready).should be_nil
|
70
|
+
@q.peek(:buried).should_not be_nil
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
74
|
it "should retry a job with a delay and then bury" do
|
75
75
|
TimeoutJob.backend = @q
|
76
76
|
TimeoutJob.new.enqueue
|
77
|
-
@q.
|
77
|
+
@q.peek(:ready).should_not be_nil
|
78
78
|
job = @q.reserve
|
79
79
|
job.beanstalk_job.stats["releases"].should eql(0)
|
80
80
|
job.beanstalk_job.stats["delay"].should eql(0)
|
81
81
|
lambda{job.perform}.should raise_exception(Quebert::Job::Timeout)
|
82
82
|
|
83
|
-
@q.
|
84
|
-
beanstalk_job = @q.
|
83
|
+
@q.peek(:ready).should be_nil
|
84
|
+
beanstalk_job = @q.peek(:delayed)
|
85
85
|
beanstalk_job.should_not be_nil
|
86
86
|
beanstalk_job.stats["releases"].should eql(1)
|
87
87
|
beanstalk_job.stats["delay"].should eql(Quebert::Controller::Beanstalk::TIMEOUT_RETRY_GROWTH_RATE**beanstalk_job.stats["releases"])
|
@@ -92,8 +92,8 @@ describe Controller::Beanstalk do
|
|
92
92
|
Quebert::Controller::Beanstalk::MAX_TIMEOUT_RETRY_DELAY = 1
|
93
93
|
lambda{@q.reserve.perform}.should raise_exception(Quebert::Job::Timeout)
|
94
94
|
|
95
|
-
@q.
|
96
|
-
@q.
|
97
|
-
@q.
|
95
|
+
@q.peek(:ready).should be_nil
|
96
|
+
@q.peek(:delayed).should be_nil
|
97
|
+
@q.peek(:buried).should_not be_nil
|
98
98
|
end
|
99
99
|
end
|
data/spec/worker_spec.rb
CHANGED
@@ -7,11 +7,11 @@ describe Worker do
|
|
7
7
|
w.backend = @q
|
8
8
|
end
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
it "should start" do
|
12
12
|
@w.start
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
context "pluggable exception handler" do
|
16
16
|
it "should raise exception if nothing is provided" do
|
17
17
|
@q.put Exceptional.new
|
@@ -20,15 +20,14 @@ describe Worker do
|
|
20
20
|
|
21
21
|
it "should default to Quebert.config.worker.exception_handler handler" do
|
22
22
|
@q.put Exceptional.new
|
23
|
-
Quebert.config.worker.exception_handler = Proc.new{|e| e.should be_instance_of(Exception) }
|
24
|
-
@w.exception_handler = Proc.new{|e| e.should be_instance_of(Exception) }
|
23
|
+
Quebert.config.worker.exception_handler = Proc.new{|e, opts| e.should be_instance_of(Exception) }
|
25
24
|
lambda{ @w.start }.should_not raise_exception
|
26
25
|
end
|
27
|
-
|
26
|
+
|
28
27
|
it "should intercept exceptions" do
|
29
28
|
@q.put Exceptional.new
|
30
|
-
@w.exception_handler = Proc.new{|e| e.should be_instance_of(Exception) }
|
29
|
+
@w.exception_handler = Proc.new{|e, opts| e.should be_instance_of(Exception) }
|
31
30
|
lambda{ @w.start }.should_not raise_exception
|
32
31
|
end
|
33
32
|
end
|
34
|
-
end
|
33
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quebert
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 59
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
+
- 11
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 1.0.9
|
10
|
+
version: 1.11.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Brad Gessler
|
@@ -17,7 +17,8 @@ autorequire:
|
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
19
|
|
20
|
-
date: 2012-
|
20
|
+
date: 2012-12-21 00:00:00 -06:00
|
21
|
+
default_executable:
|
21
22
|
dependencies:
|
22
23
|
- !ruby/object:Gem::Dependency
|
23
24
|
name: json
|
@@ -34,7 +35,7 @@ dependencies:
|
|
34
35
|
type: :runtime
|
35
36
|
version_requirements: *id001
|
36
37
|
- !ruby/object:Gem::Dependency
|
37
|
-
name:
|
38
|
+
name: beaneater
|
38
39
|
prerelease: false
|
39
40
|
requirement: &id002 !ruby/object:Gem::Requirement
|
40
41
|
none: false
|
@@ -121,6 +122,7 @@ files:
|
|
121
122
|
- spec/support/jobs.rb
|
122
123
|
- spec/support_spec.rb
|
123
124
|
- spec/worker_spec.rb
|
125
|
+
has_rdoc: true
|
124
126
|
homepage: http://github.com/polleverywhere/quebert
|
125
127
|
licenses: []
|
126
128
|
|
@@ -150,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
150
152
|
requirements: []
|
151
153
|
|
152
154
|
rubyforge_project: quebert
|
153
|
-
rubygems_version: 1.
|
155
|
+
rubygems_version: 1.6.2
|
154
156
|
signing_key:
|
155
157
|
specification_version: 3
|
156
158
|
summary: A worker queue framework built around beanstalkd
|