queue_classic_plus 0.0.2 → 1.0.0.alpha2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +8 -0
- data/Gemfile +11 -5
- data/Guardfile +77 -0
- data/README.md +75 -11
- data/Rakefile +9 -0
- data/lib/queue_classic_plus.rb +4 -6
- data/lib/queue_classic_plus/base.rb +34 -27
- data/lib/queue_classic_plus/inflector.rb +15 -0
- data/lib/queue_classic_plus/inheritable_attr.rb +35 -0
- data/lib/queue_classic_plus/new_relic.rb +35 -0
- data/lib/queue_classic_plus/queue_classic/queue.rb +11 -0
- data/lib/queue_classic_plus/update_metrics.rb +55 -34
- data/lib/queue_classic_plus/version.rb +1 -1
- data/lib/queue_classic_plus/worker.rb +6 -13
- data/queue_classic_plus.gemspec +1 -5
- data/spec/base_spec.rb +6 -7
- data/spec/helpers.rb +9 -0
- data/spec/inflector_spec.rb +18 -0
- data/spec/spec_helper.rb +8 -15
- data/spec/update_metrics_spec.rb +125 -0
- data/spec/worker_spec.rb +11 -52
- metadata +18 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5b1934a03303e576a999e71f978a68da13f0298
|
4
|
+
data.tar.gz: d1de5250aa7b2ddfe4114043d87e5a95986145f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ebadc9325c0307789f6a6216047a2c2cfe240d4364b613ee9eefc9a479224a9196813146b90ed251124cd5ad7a4966556c8fca8e1b48a490cfd806bf95124ff3
|
7
|
+
data.tar.gz: 619c736ed7b74f94841328acfa27926e2799fecc6589974d9b79de236c1f396ef126d1e80205df223e626536b69c1c0a834014e09e190b000635e571c2a8e677
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -3,9 +3,15 @@ source 'https://rubygems.org'
|
|
3
3
|
# Specify your gem's dependencies in queue_classic_plus.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
+
gem "queue_classic_matchers", github: 'rainforestapp/queue_classic_matchers'
|
7
|
+
gem 'pry'
|
6
8
|
|
7
|
-
|
8
|
-
gem
|
9
|
-
gem
|
10
|
-
|
11
|
-
|
9
|
+
group :development do
|
10
|
+
gem "guard-rspec", require: false
|
11
|
+
gem "terminal-notifier-guard"
|
12
|
+
end
|
13
|
+
|
14
|
+
group :test do
|
15
|
+
gem 'rspec'
|
16
|
+
gem 'timecop'
|
17
|
+
end
|
data/Guardfile
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec feature)
|
6
|
+
|
7
|
+
## Uncomment to clear the screen before every task
|
8
|
+
# clearing :on
|
9
|
+
|
10
|
+
## Guard internally checks for changes in the Guardfile and exits.
|
11
|
+
## If you want Guard to automatically start up again, run guard in a
|
12
|
+
## shell loop, e.g.:
|
13
|
+
##
|
14
|
+
## $ while bundle exec guard; do echo "Restarting Guard..."; done
|
15
|
+
##
|
16
|
+
## Note: if you are using the `directories` clause above and you are not
|
17
|
+
## watching the project directory ('.'), the you will want to move the Guardfile
|
18
|
+
## to a watched dir and symlink it back, e.g.
|
19
|
+
#
|
20
|
+
# $ mkdir config
|
21
|
+
# $ mv Guardfile config/
|
22
|
+
# $ ln -s config/Guardfile .
|
23
|
+
#
|
24
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
25
|
+
|
26
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
27
|
+
# rspec may be run, below are examples of the most common uses.
|
28
|
+
# * bundler: 'bundle exec rspec'
|
29
|
+
# * bundler binstubs: 'bin/rspec'
|
30
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
31
|
+
# installed the spring binstubs per the docs)
|
32
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
33
|
+
# * 'just' rspec: 'rspec'
|
34
|
+
|
35
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
36
|
+
require "guard/rspec/dsl"
|
37
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
38
|
+
|
39
|
+
# Feel free to open issues for suggestions and improvements
|
40
|
+
|
41
|
+
# RSpec files
|
42
|
+
rspec = dsl.rspec
|
43
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
44
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
45
|
+
watch(rspec.spec_files)
|
46
|
+
|
47
|
+
# Ruby files
|
48
|
+
ruby = dsl.ruby
|
49
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
50
|
+
|
51
|
+
# Rails files
|
52
|
+
rails = dsl.rails(view_extensions: %w(erb haml slim))
|
53
|
+
dsl.watch_spec_files_for(rails.app_files)
|
54
|
+
dsl.watch_spec_files_for(rails.views)
|
55
|
+
|
56
|
+
watch(rails.controllers) do |m|
|
57
|
+
[
|
58
|
+
rspec.spec.("routing/#{m[1]}_routing"),
|
59
|
+
rspec.spec.("controllers/#{m[1]}_controller"),
|
60
|
+
rspec.spec.("acceptance/#{m[1]}")
|
61
|
+
]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Rails config changes
|
65
|
+
watch(rails.spec_helper) { rspec.spec_dir }
|
66
|
+
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
|
67
|
+
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
68
|
+
|
69
|
+
# Capybara features specs
|
70
|
+
watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
|
71
|
+
|
72
|
+
# Turnip features and steps
|
73
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
74
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
75
|
+
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
|
76
|
+
end
|
77
|
+
end
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# QueueClassicPlus
|
2
2
|
|
3
|
-
|
3
|
+
[![Build Status](https://travis-ci.org/rainforestapp/queue_classic_plus.svg?branch=master)](https://travis-ci.org/rainforestapp/queue_classic_plus)
|
4
|
+
|
5
|
+
[QueueClassic](https://github.com/QueueClassic/queue_classic) is a simple Postgresql back DB queue. However, it's a little too simple to use it as the main queueing system of a medium to large app.
|
4
6
|
|
5
7
|
QueueClassicPlus adds many lacking features to QueueClassic.
|
6
8
|
|
@@ -10,13 +12,17 @@ QueueClassicPlus adds many lacking features to QueueClassic.
|
|
10
12
|
- Metrics
|
11
13
|
- Error logging / handling
|
12
14
|
- Transactions
|
15
|
+
- Rails generator to create new jobs
|
16
|
+
|
17
|
+
## Compatibility
|
18
|
+
|
19
|
+
This version of the matchers are compatible with queue_classic 3.1+ which includes built-in scheduling. See other branches for other compatible versions.
|
13
20
|
|
14
21
|
## Installation
|
15
22
|
|
16
23
|
Add these line to your application's Gemfile:
|
17
24
|
|
18
25
|
gem 'queue_classic_plus'
|
19
|
-
gem "queue_classic-later", github: "dpiddy/queue_classic-later"
|
20
26
|
|
21
27
|
And then execute:
|
22
28
|
|
@@ -33,7 +39,7 @@ Run the migration
|
|
33
39
|
### Create a new job
|
34
40
|
|
35
41
|
```bash
|
36
|
-
rails g
|
42
|
+
rails g qc_plus_job test_job
|
37
43
|
```
|
38
44
|
|
39
45
|
```ruby
|
@@ -54,17 +60,67 @@ end
|
|
54
60
|
Jobs::MyJob.do(1, "foo")
|
55
61
|
|
56
62
|
# You can also schedule a job in the future by doing
|
57
|
-
|
58
63
|
Jobs::MyJob.enqueue_perform_in(1.hour, 1, "foo")
|
59
64
|
```
|
60
65
|
|
61
66
|
### Run the QueueClassicPlus worker
|
62
67
|
|
68
|
+
QueueClassicPlus ships with its own worker and a rake task to run it. You need to use this worker to take advance of many features of QueueClassicPlus.
|
69
|
+
|
63
70
|
```
|
64
71
|
QUEUE=low bundle exec qc_plus:work
|
65
72
|
```
|
66
73
|
|
67
|
-
|
74
|
+
### Other jobs options
|
75
|
+
|
76
|
+
#### Singleton Job
|
77
|
+
|
78
|
+
It's common for background jobs to never need to be enqueed multiple time. QueueClassicPlus support these type of single jobs. Here's an example one:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
class Jobs::UpdateMetrics < QueueClassicPlus::Base
|
82
|
+
@queue = :low
|
83
|
+
|
84
|
+
# Use the lock! keyword to prevent the job from being enqueud once.
|
85
|
+
lock!
|
86
|
+
|
87
|
+
def self.perform(metric_type)
|
88
|
+
# ...
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
```
|
93
|
+
|
94
|
+
Note that `lock!` only prevents the same job from beeing enqued multiple times if the argument match.
|
95
|
+
|
96
|
+
So in our example:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
Jobs::UpdateMetrics.do 'type_a' # enqueues job
|
100
|
+
Jobs::UpdateMetrics.do 'type_a' # does not enqueues job since it's already queued
|
101
|
+
Jobs::UpdateMetrics.do 'type_b' # enqueues job as the arguments are different.
|
102
|
+
```
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
class Jobs::NoTransaction < QueueClassicPlus::Base
|
106
|
+
# Don't run the perform method in a transaction
|
107
|
+
skip_transaction!
|
108
|
+
|
109
|
+
@queue = :low
|
110
|
+
|
111
|
+
def self.perform(user_id)
|
112
|
+
# ...
|
113
|
+
end
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
#### Transaction
|
118
|
+
|
119
|
+
By default, all QueueClassicPlus jobs are executed in a PostgreSQL transaction. This decision was made because most jobs are usually pretty small and it's preferable to have all the benefits of the transaction.
|
120
|
+
|
121
|
+
You can disable this feature on a per job basis in the follwing way:
|
122
|
+
|
123
|
+
## Advanced configuration
|
68
124
|
|
69
125
|
If you want to log exceptions in your favorite exception tracker. You can configured it like sso:
|
70
126
|
|
@@ -84,14 +140,22 @@ QueueClassicPlus.update_metrics
|
|
84
140
|
|
85
141
|
Call this is a cron job or something similar.
|
86
142
|
|
87
|
-
|
143
|
+
If you are using NewRelic and want to push performance data to it, you can add this to an initializer:
|
88
144
|
|
89
|
-
|
145
|
+
```ruby
|
146
|
+
require "queue_classic_plus/new_relic"
|
147
|
+
```
|
90
148
|
|
91
149
|
## Contributing
|
92
150
|
|
93
151
|
1. Fork it ( https://github.com/[my-github-username]/queue_classic_plus/fork )
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
152
|
+
- Create your feature branch (`git checkout -b my-new-feature`)
|
153
|
+
- Commit your changes (`git commit -am 'Add some feature'`)
|
154
|
+
- Push to the branch (`git push origin my-new-feature`)
|
155
|
+
- Create a new Pull Request
|
156
|
+
|
157
|
+
### Setting up the test database
|
158
|
+
|
159
|
+
```
|
160
|
+
createdb queue_classic_plus_test
|
161
|
+
```
|
data/Rakefile
CHANGED
data/lib/queue_classic_plus.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
+
require 'logger'
|
1
2
|
require "queue_classic"
|
2
|
-
require "queue_classic/later"
|
3
|
-
require "active_record"
|
4
|
-
require "with_advisory_lock"
|
5
|
-
require "active_support/core_ext/class/attribute"
|
6
3
|
|
7
4
|
require "queue_classic_plus/version"
|
5
|
+
require "queue_classic_plus/inheritable_attr"
|
6
|
+
require "queue_classic_plus/inflector"
|
8
7
|
require "queue_classic_plus/metrics"
|
9
8
|
require "queue_classic_plus/update_metrics"
|
10
9
|
require "queue_classic_plus/base"
|
11
10
|
require "queue_classic_plus/worker"
|
11
|
+
require "queue_classic_plus/queue_classic/queue"
|
12
12
|
|
13
13
|
module QueueClassicPlus
|
14
14
|
require 'queue_classic_plus/railtie' if defined?(Rails)
|
@@ -17,14 +17,12 @@ module QueueClassicPlus
|
|
17
17
|
conn = QC::ConnAdapter.new(c)
|
18
18
|
conn.execute("ALTER TABLE queue_classic_jobs ADD COLUMN last_error TEXT")
|
19
19
|
conn.execute("ALTER TABLE queue_classic_jobs ADD COLUMN remaining_retries INTEGER")
|
20
|
-
conn.execute("ALTER TABLE queue_classic_later_jobs ADD COLUMN remaining_retries INTEGER")
|
21
20
|
end
|
22
21
|
|
23
22
|
def self.demigrate(c = QC::default_conn_adapter.connection)
|
24
23
|
conn = QC::ConnAdapter.new(c)
|
25
24
|
conn.execute("ALTER TABLE queue_classic_jobs DROP COLUMN last_error")
|
26
25
|
conn.execute("ALTER TABLE queue_classic_jobs DROP COLUMN remaining_retries")
|
27
|
-
conn.execute("ALTER TABLE queue_classic_later_jobs DROP COLUMN remaining_retries")
|
28
26
|
end
|
29
27
|
|
30
28
|
def self.exception_handler
|
@@ -1,13 +1,15 @@
|
|
1
1
|
module QueueClassicPlus
|
2
2
|
class Base
|
3
|
+
extend QueueClassicPlus::InheritableAttribute
|
4
|
+
|
3
5
|
def self.queue
|
4
6
|
QC::Queue.new(@queue)
|
5
7
|
end
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
inheritable_attr :locked
|
10
|
+
inheritable_attr :skip_transaction
|
11
|
+
inheritable_attr :retries_on
|
12
|
+
inheritable_attr :max_retries
|
11
13
|
|
12
14
|
self.max_retries = 0
|
13
15
|
self.retries_on = {}
|
@@ -39,35 +41,21 @@ module QueueClassicPlus
|
|
39
41
|
|
40
42
|
def self.can_enqueue?(method, *args)
|
41
43
|
if locked?
|
42
|
-
lock_key = [@queue, method, args].to_json
|
43
44
|
max_lock_time = ENV.fetch("QUEUE_CLASSIC_MAX_LOCK_TIME", 10 * 60).to_i
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
FROM
|
46
|
+
q = "SELECT COUNT(1) AS count
|
47
|
+
FROM
|
48
48
|
(
|
49
|
-
(
|
50
49
|
SELECT 1
|
51
50
|
FROM queue_classic_jobs
|
52
51
|
WHERE q_name = $1 AND method = $2 AND args::text = $3::text
|
53
52
|
AND (locked_at IS NULL OR locked_at > current_timestamp - interval '#{max_lock_time} seconds')
|
54
53
|
LIMIT 1
|
55
|
-
)
|
56
|
-
|
57
|
-
UNION
|
58
|
-
|
59
|
-
(
|
60
|
-
SELECT 1
|
61
|
-
FROM queue_classic_later_jobs
|
62
|
-
WHERE q_name = $4 AND method = $5 AND args = $6::text
|
63
|
-
LIMIT 1
|
64
|
-
)
|
65
54
|
)
|
66
|
-
|
55
|
+
AS x"
|
67
56
|
|
68
|
-
|
69
|
-
|
70
|
-
end
|
57
|
+
result = QC.default_conn_adapter.execute(q, @queue, method, args.to_json)
|
58
|
+
result['count'].to_i == 0
|
71
59
|
else
|
72
60
|
true
|
73
61
|
end
|
@@ -89,7 +77,7 @@ module QueueClassicPlus
|
|
89
77
|
end
|
90
78
|
|
91
79
|
def self.restart_in(time, remaining_retries, *args)
|
92
|
-
queue.
|
80
|
+
queue.enqueue_retry_in(time, "#{self.to_s}._perform", remaining_retries, *args)
|
93
81
|
end
|
94
82
|
|
95
83
|
def self.do(*args)
|
@@ -111,17 +99,36 @@ module QueueClassicPlus
|
|
111
99
|
end
|
112
100
|
|
113
101
|
def self.librato_key
|
114
|
-
(self.name || "").
|
102
|
+
Inflector.underscore(self.name || "").gsub(/\//, ".")
|
115
103
|
end
|
116
104
|
|
117
105
|
def self.transaction(options = {}, &block)
|
118
|
-
ActiveRecord
|
106
|
+
if defined?(ActiveRecord)
|
107
|
+
# If ActiveRecord is loaded, we use it's own transaction mechanisn since
|
108
|
+
# it has slightly different semanctics for rollback.
|
109
|
+
ActiveRecord::Base.transaction(options, &block)
|
110
|
+
else
|
111
|
+
begin
|
112
|
+
execute "BEGIN"
|
113
|
+
block.call
|
114
|
+
rescue
|
115
|
+
execute "ROLLBACK"
|
116
|
+
raise
|
117
|
+
end
|
118
|
+
|
119
|
+
execute "COMMIT"
|
120
|
+
end
|
119
121
|
end
|
120
122
|
|
121
123
|
# Debugging
|
122
124
|
def self.list
|
123
125
|
q = "SELECT * FROM queue_classic_jobs WHERE q_name = '#{@queue}'"
|
124
|
-
|
126
|
+
execute q
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
def self.execute(sql, *args)
|
131
|
+
QC.default_conn_adapter.execute(sql, *args)
|
125
132
|
end
|
126
133
|
end
|
127
134
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module QueueClassicPlus
|
2
|
+
module Inflector
|
3
|
+
# Storngly inspired by
|
4
|
+
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L91
|
5
|
+
def self.underscore(camel_cased_word)
|
6
|
+
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
|
7
|
+
word = camel_cased_word.to_s.gsub(/::/, '/')
|
8
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
9
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
10
|
+
word.tr!("-", "_")
|
11
|
+
word.downcase!
|
12
|
+
word
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module QueueClassicPlus
|
2
|
+
# From https://github.com/apotonick/uber/blob/master/lib/uber/inheritable_attr.rb which is MIT license
|
3
|
+
module InheritableAttribute
|
4
|
+
def inheritable_attr(name)
|
5
|
+
instance_eval %Q{
|
6
|
+
def #{name}=(v)
|
7
|
+
@#{name} = v
|
8
|
+
end
|
9
|
+
def #{name}
|
10
|
+
return @#{name} if instance_variable_defined?(:@#{name})
|
11
|
+
@#{name} = InheritableAttribute.inherit_for(self, :#{name})
|
12
|
+
end
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.inherit_for(klass, name)
|
17
|
+
return unless klass.superclass.respond_to?(name)
|
18
|
+
|
19
|
+
value = klass.superclass.send(name) # could be nil.
|
20
|
+
Clone.(value) # this could be dynamic, allowing other inheritance strategies.
|
21
|
+
end
|
22
|
+
|
23
|
+
class Clone
|
24
|
+
# The second argument allows injecting more types.
|
25
|
+
def self.call(value, uncloneable=uncloneable())
|
26
|
+
uncloneable.each { |klass| return value if value.kind_of?(klass) }
|
27
|
+
value.clone
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.uncloneable
|
31
|
+
[Symbol, TrueClass, FalseClass, NilClass]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'new_relic/agent/method_tracer'
|
2
|
+
|
3
|
+
QueueClassicPlus::Base.class_eval do
|
4
|
+
class << self
|
5
|
+
include NewRelic::Agent::Instrumentation::ControllerInstrumentation
|
6
|
+
|
7
|
+
def new_relic_key
|
8
|
+
"Custom/QueueClassicPlus/#{librato_key}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def _perform_with_new_relic(*args)
|
12
|
+
opts = {
|
13
|
+
name: 'perform',
|
14
|
+
class_name: self.name,
|
15
|
+
category: 'OtherTransaction/QueueClassicPlus',
|
16
|
+
}
|
17
|
+
|
18
|
+
perform_action_with_newrelic_trace(opts) do
|
19
|
+
if NewRelic::Agent.config[:'queue_classic_plus.capture_params']
|
20
|
+
NewRelic::Agent.add_custom_parameters(job_arguments: args)
|
21
|
+
end
|
22
|
+
_perform_without_new_relic *args
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
alias_method_chain :_perform, :new_relic
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
QueueClassicPlus::CustomWorker.class_eval do
|
31
|
+
def initialize(*)
|
32
|
+
::NewRelic::Agent.manual_start
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module QC
|
2
|
+
class Queue
|
3
|
+
def enqueue_retry_in(seconds, method, remaining_retries, *args)
|
4
|
+
QC.log_yield(:measure => 'queue.enqueue') do
|
5
|
+
s = "INSERT INTO #{TABLE_NAME} (q_name, method, args, scheduled_at, remaining_retries)
|
6
|
+
VALUES ($1, $2, $3, now() + interval '#{seconds.to_i} seconds', $4)"
|
7
|
+
res = conn_adapter.execute(s, name, method, JSON.dump(args), remaining_retries)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -1,62 +1,83 @@
|
|
1
1
|
module QueueClassicPlus
|
2
2
|
module UpdateMetrics
|
3
3
|
def self.update
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
results.each do |h|
|
14
|
-
Metrics.measure("qc.jobs_#{type}", h.fetch('count').to_i, source: h.fetch('q_name'))
|
4
|
+
metrics.each do |name, values|
|
5
|
+
if values.respond_to?(:each)
|
6
|
+
values.each do |hash|
|
7
|
+
hash.to_a.each do |(source, count)|
|
8
|
+
Metrics.measure("qc.#{name}", count, source: source)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
else
|
12
|
+
Metrics.measure("qc.#{name}", values)
|
15
13
|
end
|
16
14
|
end
|
15
|
+
end
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
def self.metrics
|
18
|
+
{
|
19
|
+
jobs_queued: jobs_queued,
|
20
|
+
jobs_scheduled: jobs_scheduled,
|
21
|
+
max_created_at: max_age("created_at", "created_at = scheduled_at"),
|
22
|
+
max_locked_at: max_age("locked_at", "locked_at IS NOT NULL"),
|
23
|
+
"max_created_at.unlocked" => max_age("locked_at", "locked_at IS NULL"),
|
24
|
+
"jobs_delayed.lag" => max_age("scheduled_at"),
|
25
|
+
"jobs_delayed.late_count" => late_count,
|
26
|
+
}
|
27
|
+
end
|
23
28
|
|
24
|
-
|
25
|
-
|
26
|
-
|
29
|
+
def self.jobs_queued
|
30
|
+
sql_group_count "SELECT q_name AS group, COUNT(1)
|
31
|
+
FROM queue_classic_jobs
|
32
|
+
WHERE scheduled_at <= NOW() GROUP BY q_name"
|
33
|
+
end
|
27
34
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
35
|
+
def self.jobs_scheduled
|
36
|
+
sql_group_count "SELECT q_name AS group, COUNT(1)
|
37
|
+
FROM queue_classic_jobs
|
38
|
+
WHERE scheduled_at > NOW() GROUP BY q_name"
|
39
|
+
end
|
32
40
|
|
33
|
-
|
41
|
+
def self.late_count
|
42
|
+
nb_late = execute("SELECT COUNT(1)
|
43
|
+
FROM queue_classic_jobs
|
44
|
+
WHERE scheduled_at < NOW() AND #{not_failed}")
|
45
|
+
nb_late ? Integer(nb_late['count']) : 0
|
46
|
+
end
|
34
47
|
|
35
|
-
|
36
|
-
FROM queue_classic_later_jobs
|
37
|
-
WHERE not_before < NOW()").first
|
38
|
-
nb_late = nb_late ? nb_late['count'] : 0
|
48
|
+
private
|
39
49
|
|
40
|
-
|
50
|
+
def self.sql_group_count(sql)
|
51
|
+
results = execute(sql)
|
52
|
+
results = [results] if Hash === results
|
53
|
+
Array(results).map do |h|
|
54
|
+
{
|
55
|
+
h.fetch("group") => Integer(h.fetch('count'))
|
56
|
+
}
|
41
57
|
end
|
42
58
|
end
|
43
59
|
|
44
|
-
private
|
45
60
|
def self.max_age(column, *conditions)
|
46
|
-
conditions.unshift
|
61
|
+
conditions.unshift not_failed
|
62
|
+
conditions.unshift "scheduled_at <= NOW()"
|
47
63
|
|
48
|
-
q = "SELECT EXTRACT(EPOCH FROM now() - #{column}) AS age_in_seconds
|
64
|
+
q = "SELECT EXTRACT(EPOCH FROM now() - #{column}) AS age_in_seconds
|
49
65
|
FROM queue_classic_jobs
|
50
66
|
WHERE #{conditions.join(" AND ")}
|
51
67
|
ORDER BY age_in_seconds DESC
|
68
|
+
LIMIT 1
|
52
69
|
"
|
53
|
-
age_info = execute(q)
|
70
|
+
age_info = execute(q)
|
54
71
|
|
55
72
|
age_info ? age_info['age_in_seconds'].to_i : 0
|
56
73
|
end
|
57
74
|
|
58
75
|
def self.execute(q)
|
59
|
-
|
76
|
+
QC.default_conn_adapter.execute(q)
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.not_failed
|
80
|
+
"q_name != '#{::QueueClassicPlus::CustomWorker::FailedQueue.name}'"
|
60
81
|
end
|
61
82
|
end
|
62
83
|
end
|
@@ -1,18 +1,12 @@
|
|
1
1
|
module QueueClassicPlus
|
2
2
|
class CustomWorker < QC::Worker
|
3
|
-
class QueueClassicJob < ActiveRecord::Base
|
4
|
-
end
|
5
|
-
|
6
3
|
BACKOFF_WIDTH = 10
|
7
4
|
FailedQueue = QC::Queue.new("failed_jobs")
|
8
5
|
|
9
6
|
def enqueue_failed(job, e)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
new_job.last_error = if e.backtrace then ([e.message] + e.backtrace ).join("\n") else e.message end
|
14
|
-
new_job.save!
|
15
|
-
end
|
7
|
+
sql = "INSERT INTO #{QC::TABLE_NAME} (q_name, method, args, last_error) VALUES ('failed_jobs', $1, $2, $3)"
|
8
|
+
last_error = e.backtrace ? ([e.message] + e.backtrace ).join("\n") : e.message
|
9
|
+
QC.default_conn_adapter.execute sql, job[:method], JSON.dump(job[:args]), last_error
|
16
10
|
|
17
11
|
QueueClassicPlus.exception_handler.call(e, job)
|
18
12
|
Metrics.increment("qc.errors", source: @q_name)
|
@@ -22,11 +16,9 @@ module QueueClassicPlus
|
|
22
16
|
QueueClassicPlus.logger.info "Handling exception #{e.message} for job #{job[:id]}"
|
23
17
|
klass = job_klass(job)
|
24
18
|
|
25
|
-
model = QueueClassicJob.find(job[:id])
|
26
|
-
|
27
19
|
# The mailers doesn't have a retries_on?
|
28
20
|
if klass && klass.respond_to?(:retries_on?) && klass.retries_on?(e)
|
29
|
-
remaining_retries =
|
21
|
+
remaining_retries = job[:remaining_retries] || klass.max_retries
|
30
22
|
remaining_retries -= 1
|
31
23
|
|
32
24
|
if remaining_retries > 0
|
@@ -39,7 +31,8 @@ module QueueClassicPlus
|
|
39
31
|
else
|
40
32
|
enqueue_failed(job, e)
|
41
33
|
end
|
42
|
-
|
34
|
+
|
35
|
+
FailedQueue.delete(job[:id])
|
43
36
|
end
|
44
37
|
|
45
38
|
private
|
data/queue_classic_plus.gemspec
CHANGED
@@ -18,11 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "queue_classic"
|
22
|
-
spec.add_dependency "queue_classic-later"
|
23
|
-
spec.add_dependency "activerecord", "> 3.0"
|
24
|
-
spec.add_dependency "activesupport", "> 3.0"
|
25
|
-
spec.add_dependency "with_advisory_lock"
|
21
|
+
spec.add_dependency "queue_classic", ">= 3.1.0"
|
26
22
|
spec.add_development_dependency "bundler", "~> 1.6"
|
27
23
|
spec.add_development_dependency "rake"
|
28
24
|
end
|
data/spec/base_spec.rb
CHANGED
@@ -17,9 +17,8 @@ describe QueueClassicPlus::Base do
|
|
17
17
|
|
18
18
|
it "does allow multiple enqueues if something got locked for too long" do
|
19
19
|
subject.do
|
20
|
-
|
21
|
-
|
22
|
-
"
|
20
|
+
one_day_ago = Time.now - 60*60*24
|
21
|
+
execute "UPDATE queue_classic_jobs SET locked_at = '#{one_day_ago}' WHERE q_name = 'test'"
|
23
22
|
subject.do
|
24
23
|
subject.should have_queue_size_of(2)
|
25
24
|
end
|
@@ -40,8 +39,8 @@ describe QueueClassicPlus::Base do
|
|
40
39
|
end
|
41
40
|
|
42
41
|
it "calls perform in a transaction" do
|
43
|
-
|
44
|
-
subject._perform
|
42
|
+
QueueClassicPlus::Base.should_receive(:transaction).and_call_original
|
43
|
+
subject._perform
|
45
44
|
end
|
46
45
|
|
47
46
|
it "measures the time" do
|
@@ -62,8 +61,8 @@ describe QueueClassicPlus::Base do
|
|
62
61
|
end
|
63
62
|
|
64
63
|
it "calls perform outside of a transaction" do
|
65
|
-
|
66
|
-
subject._perform
|
64
|
+
QueueClassicPlus::Base.should_not_receive(:transaction)
|
65
|
+
subject._perform
|
67
66
|
end
|
68
67
|
end
|
69
68
|
|
data/spec/helpers.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module QueueClassicPlus
|
4
|
+
describe Inflector do
|
5
|
+
describe ".underscore" do
|
6
|
+
{
|
7
|
+
"foo" => "foo",
|
8
|
+
"Foo" => "foo",
|
9
|
+
"FooBar" => "foo_bar",
|
10
|
+
"Foo::Bar" => "foo/bar"
|
11
|
+
}.each do |word, expected|
|
12
|
+
it "converst #{word} to #{expected}" do
|
13
|
+
expect(described_class.underscore(word)).to eq(expected)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -3,29 +3,22 @@ require 'pg'
|
|
3
3
|
require 'timecop'
|
4
4
|
require 'queue_classic_matchers'
|
5
5
|
require_relative './sample_jobs'
|
6
|
+
require_relative './helpers'
|
7
|
+
require 'pry'
|
8
|
+
|
9
|
+
ENV["DATABASE_URL"] ||= "postgres:///queue_classic_plus_test"
|
6
10
|
|
7
11
|
RSpec.configure do |config|
|
8
|
-
config.
|
9
|
-
ActiveRecord::Base.establish_connection(
|
10
|
-
:adapter => "postgresql",
|
11
|
-
:username => "postgres",
|
12
|
-
:database => "queue_classic_plus_test",
|
13
|
-
:host => 'localhost',
|
14
|
-
)
|
12
|
+
config.include QcHelpers
|
15
13
|
|
16
|
-
|
14
|
+
config.before(:suite) do
|
15
|
+
QC.default_conn_adapter.execute "drop schema public cascade; create schema public;"
|
17
16
|
|
18
|
-
QC.default_conn_adapter = QC::ConnAdapter.new(ActiveRecord::Base.connection.raw_connection)
|
19
17
|
QC::Setup.create
|
20
|
-
QC::Later::Setup.create
|
21
18
|
QueueClassicPlus.migrate
|
22
19
|
end
|
23
20
|
|
24
21
|
config.before(:each) do
|
25
|
-
|
26
|
-
table != "schema_migrations"
|
27
|
-
end
|
28
|
-
ActiveRecord::Base.connection.execute("TRUNCATE #{tables.join(', ')} CASCADE") unless tables.empty?
|
29
|
-
|
22
|
+
QC.default_conn_adapter.execute "TRUNCATE queue_classic_jobs;"
|
30
23
|
end
|
31
24
|
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe QueueClassicPlus::UpdateMetrics do
|
4
|
+
describe ".update" do
|
5
|
+
it "works" do
|
6
|
+
QC.enqueue "puts"
|
7
|
+
|
8
|
+
expect(QueueClassicPlus::Metrics).to(receive(:measure)).at_least(1).times do |metric, value, options|
|
9
|
+
expect(metric).to_not be_nil
|
10
|
+
expect(value).to_not be_nil
|
11
|
+
end
|
12
|
+
described_class.update
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe ".metrics" do
|
17
|
+
subject { described_class.metrics }
|
18
|
+
|
19
|
+
before do
|
20
|
+
QC.enqueue "puts"
|
21
|
+
QC.enqueue "puts", 2
|
22
|
+
QC.enqueue_in 60, "puts", 2
|
23
|
+
end
|
24
|
+
|
25
|
+
context "jobs_queued" do
|
26
|
+
it "returns the number of jobs group per queue" do
|
27
|
+
expect(subject[:jobs_queued]).to eq([{"default" => 2}])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "jobs_scheduled" do
|
32
|
+
it "returns the number of jobs group per queue" do
|
33
|
+
expect(subject[:jobs_scheduled]).to eq([{"default" => 1}])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "max_locked_at" do
|
38
|
+
it "zero if nothing is locked" do
|
39
|
+
max = subject[:max_locked_at]
|
40
|
+
expect(max).to eq(0)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns the age of the oldest lock" do
|
44
|
+
execute "UPDATE queue_classic_jobs SET locked_at = '#{Time.now - 60}'"
|
45
|
+
|
46
|
+
max = subject[:max_locked_at]
|
47
|
+
expect(59..61).to include(max)
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'scheduled jobs' do
|
51
|
+
it 'reports the correct max_locked_at' do
|
52
|
+
execute "UPDATE queue_classic_jobs SET locked_at = '#{Time.now - 30}', scheduled_at = '#{Time.now - 60}', created_at = '#{Time.now - 5*60}'"
|
53
|
+
|
54
|
+
expect(subject[:max_locked_at]).to be_within(1).of(30)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "max_created_at" do
|
60
|
+
it "returns a small positive value" do
|
61
|
+
max = subject[:max_created_at]
|
62
|
+
expect(0..0.2).to include(max)
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'scheduled jobs' do
|
66
|
+
it "ignores jobs schedule in the future" do
|
67
|
+
execute "UPDATE queue_classic_jobs SET created_at = '#{Time.now - 60}', scheduled_at = '#{Time.now + 60}'"
|
68
|
+
|
69
|
+
max = subject[:max_created_at]
|
70
|
+
expect(0..0.2).to include(max)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'reports time only for jobs that were never scheduled for future' do
|
74
|
+
execute "DELETE FROM queue_classic_jobs"
|
75
|
+
QC.enqueue 'puts'
|
76
|
+
QC.enqueue_in 1, 'puts'
|
77
|
+
one_min_ago = Time.now - 60
|
78
|
+
execute "UPDATE queue_classic_jobs SET created_at = '#{one_min_ago}', scheduled_at = '#{one_min_ago}'"
|
79
|
+
expect(subject[:max_created_at]).to be_within(1).of(60)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "max_created_at.unlocked" do
|
85
|
+
it "ignores lock jobs" do
|
86
|
+
execute "UPDATE queue_classic_jobs SET locked_at = '#{Time.now - 60}', created_at = '#{Time.now - 2*60}'"
|
87
|
+
|
88
|
+
max = subject["max_created_at.unlocked"]
|
89
|
+
expect(max).to eq(0)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "jobs_delayed.lag" do
|
94
|
+
it "returns the maximum different between the scheduled time and now" do
|
95
|
+
execute "UPDATE queue_classic_jobs SET scheduled_at = '#{Time.now - 60}'"
|
96
|
+
|
97
|
+
lag = subject["jobs_delayed.lag"]
|
98
|
+
expect(lag).to be_within(1.0).of(60)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "ignores jobs scheduled in the future" do
|
102
|
+
execute "UPDATE queue_classic_jobs SET scheduled_at = '#{Time.now + 60}'"
|
103
|
+
|
104
|
+
lag = subject["jobs_delayed.lag"]
|
105
|
+
expect(lag).to eq(0)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "ignores the failed queue" do
|
109
|
+
execute "UPDATE queue_classic_jobs SET scheduled_at = '#{Time.now - 60}', q_name = 'failed_jobs'"
|
110
|
+
|
111
|
+
lag = subject["jobs_delayed.lag"]
|
112
|
+
expect(lag).to eq(0)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "jobs_delayed.late_count" do
|
117
|
+
it "returns the jobs that a created in the future" do
|
118
|
+
count = subject["jobs_delayed.late_count"]
|
119
|
+
# All jobs are always late because enqueue sets the value of
|
120
|
+
# scheduled_at to now() for normal jobs
|
121
|
+
expect(count).to eq(2)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/spec/worker_spec.rb
CHANGED
@@ -1,12 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe QueueClassicPlus::CustomWorker do
|
4
|
-
class QueueClassicLaterJob < ActiveRecord::Base
|
5
|
-
end
|
6
|
-
|
7
|
-
class QueueClassicJob < ActiveRecord::Base
|
8
|
-
end
|
9
|
-
|
10
4
|
let(:failed_queue) { described_class::FailedQueue }
|
11
5
|
|
12
6
|
context "failure" do
|
@@ -21,7 +15,9 @@ describe QueueClassicPlus::CustomWorker do
|
|
21
15
|
job = failed_queue.lock
|
22
16
|
job[:method].should == "Kerklfadsjflaksj"
|
23
17
|
job[:args].should == [1, 2, 3]
|
24
|
-
|
18
|
+
full_job = find_job(job[:id])
|
19
|
+
|
20
|
+
full_job['last_error'].should_not be_nil
|
25
21
|
end
|
26
22
|
|
27
23
|
it "records normal errors" do
|
@@ -35,7 +31,7 @@ describe QueueClassicPlus::CustomWorker do
|
|
35
31
|
context "retry" do
|
36
32
|
let(:job_type) { Jobs::Tests::LockedTestJob }
|
37
33
|
let(:worker) { described_class.new q_name: job_type.queue.name }
|
38
|
-
let(:enqueue_expected_ts) { described_class::BACKOFF_WIDTH
|
34
|
+
let(:enqueue_expected_ts) { Time.now + described_class::BACKOFF_WIDTH }
|
39
35
|
|
40
36
|
before do
|
41
37
|
job_type.skip_transaction!
|
@@ -51,16 +47,14 @@ describe QueueClassicPlus::CustomWorker do
|
|
51
47
|
QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::LockedTestJob._perform', [true]).first['remaining_retries'].should be_nil
|
52
48
|
|
53
49
|
Timecop.freeze do
|
54
|
-
|
55
|
-
worker.work
|
56
|
-
end.to change_queue_size_of(job_type).by(-1)
|
50
|
+
worker.work
|
57
51
|
|
58
52
|
failed_queue.count.should == 0 # not enqueued on Failed
|
59
|
-
Jobs::Tests::LockedTestJob.
|
53
|
+
QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::LockedTestJob._perform', [true]).first['remaining_retries'].should eq "4"
|
54
|
+
Jobs::Tests::LockedTestJob.should have_scheduled(true).at(Time.now + described_class::BACKOFF_WIDTH) # should have scheduled a retry for later
|
60
55
|
end
|
61
56
|
|
62
|
-
Timecop.freeze(Time.now + (described_class::BACKOFF_WIDTH
|
63
|
-
QC::Later.tick(true)
|
57
|
+
Timecop.freeze(Time.now + (described_class::BACKOFF_WIDTH * 2)) do
|
64
58
|
# the job should be re-enqueued with a decremented retry count
|
65
59
|
jobs = QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::LockedTestJob._perform', [true])
|
66
60
|
jobs.size.should == 1
|
@@ -82,46 +76,11 @@ describe QueueClassicPlus::CustomWorker do
|
|
82
76
|
QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::LockedTestJob._perform', [true]).first['remaining_retries'].should be_nil
|
83
77
|
|
84
78
|
Timecop.freeze do
|
85
|
-
|
86
|
-
worker.work
|
87
|
-
end.to change_queue_size_of(job_type).by(-1)
|
79
|
+
worker.work
|
88
80
|
|
81
|
+
QueueClassicMatchers::QueueClassicRspec.find_by_args('failed_jobs', 'Jobs::Tests::LockedTestJob._perform', [true]).first['remaining_retries'].should be_nil
|
89
82
|
failed_queue.count.should == 1 # not enqueued on Failed
|
90
|
-
Jobs::Tests::LockedTestJob.should_not have_scheduled(true).at(Time.now + described_class::BACKOFF_WIDTH
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
context "enqueuing during a retry" do
|
96
|
-
let(:job_type) { Jobs::Tests::LockedTestJob }
|
97
|
-
let(:worker) { described_class.new q_name: job_type.queue.name }
|
98
|
-
let(:enqueue_expected_ts) { described_class::BACKOFF_WIDTH.seconds.from_now }
|
99
|
-
|
100
|
-
before do
|
101
|
-
job_type.max_retries = 5
|
102
|
-
job_type.skip_transaction!
|
103
|
-
end
|
104
|
-
|
105
|
-
it "does not enqueue in main queue while retrying" do
|
106
|
-
expect do
|
107
|
-
job_type.enqueue_perform(true)
|
108
|
-
end.to change_queue_size_of(job_type).by(1)
|
109
|
-
|
110
|
-
Jobs::Tests::LockedTestJob.should have_queue_size_of(1)
|
111
|
-
failed_queue.count.should == 0
|
112
|
-
QueueClassicMatchers::QueueClassicRspec.find_by_args('low', 'Jobs::Tests::LockedTestJob._perform', [true]).first['remaining_retries'].should be_nil
|
113
|
-
|
114
|
-
Timecop.freeze do
|
115
|
-
expect do
|
116
|
-
worker.work
|
117
|
-
end.to change_queue_size_of(job_type).by(-1)
|
118
|
-
|
119
|
-
failed_queue.count.should == 0 # not enqueued on Failed
|
120
|
-
Jobs::Tests::LockedTestJob.should have_scheduled(true).at(Time.now + described_class::BACKOFF_WIDTH.seconds.to_i) # should have scheduled a retry for later
|
121
|
-
|
122
|
-
expect do
|
123
|
-
job_type.enqueue_perform(true)
|
124
|
-
end.to change_queue_size_of(job_type).by(0)
|
83
|
+
Jobs::Tests::LockedTestJob.should_not have_scheduled(true).at(Time.now + described_class::BACKOFF_WIDTH) # should have scheduled a retry for later
|
125
84
|
end
|
126
85
|
end
|
127
86
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: queue_classic_plus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 1.0.0.alpha2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simon Mathieu
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2015-02-21 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: queue_classic
|
@@ -18,70 +18,14 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ">="
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
22
|
-
type: :runtime
|
23
|
-
prerelease: false
|
24
|
-
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
requirements:
|
26
|
-
- - ">="
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
version: '0'
|
29
|
-
- !ruby/object:Gem::Dependency
|
30
|
-
name: queue_classic-later
|
31
|
-
requirement: !ruby/object:Gem::Requirement
|
32
|
-
requirements:
|
33
|
-
- - ">="
|
34
|
-
- !ruby/object:Gem::Version
|
35
|
-
version: '0'
|
36
|
-
type: :runtime
|
37
|
-
prerelease: false
|
38
|
-
version_requirements: !ruby/object:Gem::Requirement
|
39
|
-
requirements:
|
40
|
-
- - ">="
|
41
|
-
- !ruby/object:Gem::Version
|
42
|
-
version: '0'
|
43
|
-
- !ruby/object:Gem::Dependency
|
44
|
-
name: activerecord
|
45
|
-
requirement: !ruby/object:Gem::Requirement
|
46
|
-
requirements:
|
47
|
-
- - ">"
|
48
|
-
- !ruby/object:Gem::Version
|
49
|
-
version: '3.0'
|
50
|
-
type: :runtime
|
51
|
-
prerelease: false
|
52
|
-
version_requirements: !ruby/object:Gem::Requirement
|
53
|
-
requirements:
|
54
|
-
- - ">"
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
version: '3.0'
|
57
|
-
- !ruby/object:Gem::Dependency
|
58
|
-
name: activesupport
|
59
|
-
requirement: !ruby/object:Gem::Requirement
|
60
|
-
requirements:
|
61
|
-
- - ">"
|
62
|
-
- !ruby/object:Gem::Version
|
63
|
-
version: '3.0'
|
64
|
-
type: :runtime
|
65
|
-
prerelease: false
|
66
|
-
version_requirements: !ruby/object:Gem::Requirement
|
67
|
-
requirements:
|
68
|
-
- - ">"
|
69
|
-
- !ruby/object:Gem::Version
|
70
|
-
version: '3.0'
|
71
|
-
- !ruby/object:Gem::Dependency
|
72
|
-
name: with_advisory_lock
|
73
|
-
requirement: !ruby/object:Gem::Requirement
|
74
|
-
requirements:
|
75
|
-
- - ">="
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version: '0'
|
21
|
+
version: 3.1.0
|
78
22
|
type: :runtime
|
79
23
|
prerelease: false
|
80
24
|
version_requirements: !ruby/object:Gem::Requirement
|
81
25
|
requirements:
|
82
26
|
- - ">="
|
83
27
|
- !ruby/object:Gem::Version
|
84
|
-
version:
|
28
|
+
version: 3.1.0
|
85
29
|
- !ruby/object:Gem::Dependency
|
86
30
|
name: bundler
|
87
31
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,13 +66,19 @@ files:
|
|
122
66
|
- ".gitignore"
|
123
67
|
- ".rspec"
|
124
68
|
- ".rvmrc"
|
69
|
+
- ".travis.yml"
|
125
70
|
- Gemfile
|
71
|
+
- Guardfile
|
126
72
|
- LICENSE.txt
|
127
73
|
- README.md
|
128
74
|
- Rakefile
|
129
75
|
- lib/queue_classic_plus.rb
|
130
76
|
- lib/queue_classic_plus/base.rb
|
77
|
+
- lib/queue_classic_plus/inflector.rb
|
78
|
+
- lib/queue_classic_plus/inheritable_attr.rb
|
131
79
|
- lib/queue_classic_plus/metrics.rb
|
80
|
+
- lib/queue_classic_plus/new_relic.rb
|
81
|
+
- lib/queue_classic_plus/queue_classic/queue.rb
|
132
82
|
- lib/queue_classic_plus/railtie.rb
|
133
83
|
- lib/queue_classic_plus/tasks/work.rake
|
134
84
|
- lib/queue_classic_plus/update_metrics.rb
|
@@ -139,8 +89,11 @@ files:
|
|
139
89
|
- lib/rails/generators/qc_plus_job/templates/job_spec.rb.erb
|
140
90
|
- queue_classic_plus.gemspec
|
141
91
|
- spec/base_spec.rb
|
92
|
+
- spec/helpers.rb
|
93
|
+
- spec/inflector_spec.rb
|
142
94
|
- spec/sample_jobs.rb
|
143
95
|
- spec/spec_helper.rb
|
96
|
+
- spec/update_metrics_spec.rb
|
144
97
|
- spec/worker_spec.rb
|
145
98
|
homepage: ''
|
146
99
|
licenses:
|
@@ -157,9 +110,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
157
110
|
version: '0'
|
158
111
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
159
112
|
requirements:
|
160
|
-
- - "
|
113
|
+
- - ">"
|
161
114
|
- !ruby/object:Gem::Version
|
162
|
-
version:
|
115
|
+
version: 1.3.1
|
163
116
|
requirements: []
|
164
117
|
rubyforge_project:
|
165
118
|
rubygems_version: 2.2.2
|
@@ -168,6 +121,9 @@ specification_version: 4
|
|
168
121
|
summary: Useful extras for Queue Classic
|
169
122
|
test_files:
|
170
123
|
- spec/base_spec.rb
|
124
|
+
- spec/helpers.rb
|
125
|
+
- spec/inflector_spec.rb
|
171
126
|
- spec/sample_jobs.rb
|
172
127
|
- spec/spec_helper.rb
|
128
|
+
- spec/update_metrics_spec.rb
|
173
129
|
- spec/worker_spec.rb
|