queue_classic_plus 0.0.2 → 1.0.0.alpha2
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.
- 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
|
+
[](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
|