active_job_resque_solo 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/lib/active_job/plugins/resque/solo.rb +7 -92
- data/lib/active_job/plugins/resque/solo/inspector.rb +104 -0
- data/lib/active_job_resque_solo/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f760861754e12e2de29870716638d38d93ceaa09
|
|
4
|
+
data.tar.gz: 553f61b9c2b9653afc9217dc45f8439273311a06
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e558f3fef406f0bf9b39740b697c05e889dda701a6aecc24684c6dd5106ce9b28ffd163da394ede37bcd972d9f9590033f1ed51ff786dc7f0ae4fe7c965fe7ab
|
|
7
|
+
data.tar.gz: f671a673aa14aacece35e289639331fd3dee4b5a723c7fa4efdce1ec10c806d71e194f2b0820aa6f8dc11e457e4c668bfb8cf998fad43c3c0c7829c05c15b1f2
|
data/README.md
CHANGED
|
@@ -38,8 +38,8 @@ Solo will prevent a new instance of the job from being enqueued.
|
|
|
38
38
|
|
|
39
39
|
You can control which named arguments are used to determine uniqueness in the queue:
|
|
40
40
|
|
|
41
|
-
`solo_only_args`
|
|
42
|
-
`solo_except_args`
|
|
41
|
+
* `solo_only_args`
|
|
42
|
+
* `solo_except_args`
|
|
43
43
|
|
|
44
44
|
```ruby
|
|
45
45
|
class MyJob < ActiveJob::Base
|
|
@@ -79,9 +79,9 @@ While this plugin will greatly reduce duplicate instances of a job from being
|
|
|
79
79
|
enqueued, there are two scenarios where duplicates may still be enqueued,
|
|
80
80
|
so be sure to check out other gems for locking if your job is not idempotent.
|
|
81
81
|
|
|
82
|
-
1. When multiple processes simultaneously attempt to enqueue the same job two or
|
|
83
|
-
more may be enqueued.
|
|
84
|
-
2. If your queue has many jobs, and workers remove a job
|
|
82
|
+
1. When multiple processes simultaneously attempt to enqueue the same job, two or
|
|
83
|
+
more instances may be enqueued.
|
|
84
|
+
2. If your queue has many jobs, and workers remove a job while Solo scans
|
|
85
85
|
the queue, it's possible for the original enqueued job to be missed. Solo will allow
|
|
86
86
|
the new instance of the job to be enqueued.
|
|
87
87
|
|
|
@@ -1,115 +1,30 @@
|
|
|
1
|
+
require_relative 'solo/inspector'
|
|
2
|
+
|
|
1
3
|
module ActiveJob
|
|
2
4
|
module Plugins
|
|
3
5
|
module Resque
|
|
4
6
|
module Solo
|
|
5
|
-
|
|
6
7
|
def self.included(base_class)
|
|
7
8
|
base_class.extend(ClassMethods)
|
|
8
9
|
|
|
9
10
|
base_class.around_enqueue do |job, block|
|
|
10
|
-
base_class.
|
|
11
|
+
base_class.solo_inspector.around_enqueue(job, block)
|
|
11
12
|
end
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
module ClassMethods
|
|
15
|
-
def resque_present?
|
|
16
|
-
ActiveJob::Base.queue_adapter == ActiveJob::QueueAdapters::ResqueAdapter
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def solo_around_enqueue(job, block)
|
|
20
|
-
if resque_present?
|
|
21
|
-
# always ignore the ActiveJob symbol hash key.
|
|
22
|
-
@solo_except_args ||= []
|
|
23
|
-
@solo_except_args << "_aj_symbol_keys" unless @solo_except_args.include?("_aj_symbol_keys")
|
|
24
|
-
|
|
25
|
-
if !solo_job_enqueued?(job) && !solo_job_executing?(job)
|
|
26
|
-
block.call
|
|
27
|
-
end
|
|
28
|
-
else
|
|
29
|
-
# if resque is not present, always enqueue
|
|
30
|
-
block.call
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
16
|
def solo_only_args(*args)
|
|
35
17
|
@solo_only_args = args.compact.map(&:to_s).uniq
|
|
36
|
-
raise "
|
|
18
|
+
raise ArgumentError, "solo_only_args requires one or more field names" if @solo_only_args.empty?
|
|
37
19
|
end
|
|
38
20
|
|
|
39
21
|
def solo_except_args(*args)
|
|
40
22
|
@solo_except_args = args.compact.map(&:to_s).uniq
|
|
41
|
-
raise "
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def solo_job_enqueued?(job)
|
|
45
|
-
size = ::Resque.size(job.queue_name)
|
|
46
|
-
return false if size.zero?
|
|
47
|
-
|
|
48
|
-
page_size = 250
|
|
49
|
-
pages = (size/page_size).to_i + 1
|
|
50
|
-
jobs = []
|
|
51
|
-
|
|
52
|
-
# It's possible for this loop to skip jobs if they
|
|
53
|
-
# are dequeued while the loop is in progress.
|
|
54
|
-
(0..pages).each do |i|
|
|
55
|
-
page_start = i * page_size
|
|
56
|
-
page = ::Resque.peek(job.queue_name, page_start, page_size)
|
|
57
|
-
break if page.empty?
|
|
58
|
-
jobs += page
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
job_class, job_arguments = solo_job(job)
|
|
62
|
-
|
|
63
|
-
(jobs.size-1).downto(0) do |i|
|
|
64
|
-
scheduled_job = jobs[i]
|
|
65
|
-
return true if solo_job_enqueued_with_args?(job_class, job_arguments, scheduled_job)
|
|
66
|
-
end
|
|
67
|
-
false
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def solo_job_executing?(job)
|
|
71
|
-
job_class, job_arguments = solo_job(job)
|
|
72
|
-
|
|
73
|
-
::Resque.workers.any? do |worker|
|
|
74
|
-
processing = worker.processing
|
|
75
|
-
next false if processing.blank?
|
|
76
|
-
args = processing["payload"]["args"][0]
|
|
77
|
-
solo_job_with_args_eq?(job_class, job_arguments, args)
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def solo_job_enqueued_with_args?(job_class, job_arguments, scheduled_job)
|
|
82
|
-
args = scheduled_job["args"][0]
|
|
83
|
-
solo_job_with_args_eq?(job_class, job_arguments, args)
|
|
23
|
+
raise ArgumentError, "solo_except_args requires one or more field names" if @solo_except_args.empty?
|
|
84
24
|
end
|
|
85
25
|
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
encoded_arguments = wrapper_args['arguments']
|
|
89
|
-
encoded_arguments = solo_job_args(encoded_arguments)
|
|
90
|
-
encoded_arguments == job_arguments
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def solo_job(job)
|
|
94
|
-
job_arguments = ActiveJob::Arguments.serialize(job.arguments)
|
|
95
|
-
job_arguments = solo_job_args(job_arguments)
|
|
96
|
-
job_class = job.class.name
|
|
97
|
-
[ job_class, job_arguments ]
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def solo_job_args(args)
|
|
101
|
-
if args.present?
|
|
102
|
-
args.map do |arg|
|
|
103
|
-
if arg.is_a? Hash
|
|
104
|
-
arg.keep_if { |k,v| @solo_only_args.include?(k.to_s) } if @solo_only_args.present?
|
|
105
|
-
arg.keep_if { |k,v| !@solo_except_args.include?(k.to_s) } if @solo_except_args.present?
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
arg
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
args
|
|
26
|
+
def solo_inspector
|
|
27
|
+
@solo_inspector ||= Inspector.new(@solo_only_args, @solo_except_args)
|
|
113
28
|
end
|
|
114
29
|
end
|
|
115
30
|
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module ActiveJob
|
|
2
|
+
module Plugins
|
|
3
|
+
module Resque
|
|
4
|
+
module Solo
|
|
5
|
+
class Inspector
|
|
6
|
+
|
|
7
|
+
def initialize(only_args, except_args)
|
|
8
|
+
@only_args = only_args
|
|
9
|
+
@except_args = except_args || []
|
|
10
|
+
# always ignore the ActiveJob symbol hash key.
|
|
11
|
+
@except_args << "_aj_symbol_keys" unless @except_args.include?("_aj_symbol_keys")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.resque_present?
|
|
15
|
+
ActiveJob::Base.queue_adapter == ActiveJob::QueueAdapters::ResqueAdapter
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def around_enqueue(job, block)
|
|
19
|
+
if Inspector::resque_present?
|
|
20
|
+
|
|
21
|
+
if !job_enqueued?(job) && !job_executing?(job)
|
|
22
|
+
block.call
|
|
23
|
+
end
|
|
24
|
+
else
|
|
25
|
+
# if resque is not present, always enqueue
|
|
26
|
+
block.call
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def job_enqueued?(job)
|
|
31
|
+
size = ::Resque.size(job.queue_name)
|
|
32
|
+
return false if size.zero?
|
|
33
|
+
|
|
34
|
+
page_size = 250
|
|
35
|
+
pages = (size/page_size).to_i + 1
|
|
36
|
+
jobs = []
|
|
37
|
+
|
|
38
|
+
# It's possible for this loop to skip jobs if they
|
|
39
|
+
# are dequeued while the loop is in progress.
|
|
40
|
+
(0..pages).each do |i|
|
|
41
|
+
page_start = i * page_size
|
|
42
|
+
page = ::Resque.peek(job.queue_name, page_start, page_size)
|
|
43
|
+
break if page.empty?
|
|
44
|
+
jobs += page
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
job_class, job_arguments = job(job)
|
|
48
|
+
|
|
49
|
+
(jobs.size-1).downto(0) do |i|
|
|
50
|
+
scheduled_job = jobs[i]
|
|
51
|
+
return true if job_enqueued_with_args?(job_class, job_arguments, scheduled_job)
|
|
52
|
+
end
|
|
53
|
+
false
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def job_executing?(job)
|
|
57
|
+
job_class, job_arguments = job(job)
|
|
58
|
+
|
|
59
|
+
::Resque.workers.any? do |worker|
|
|
60
|
+
processing = worker.processing
|
|
61
|
+
next false if processing.blank?
|
|
62
|
+
args = processing["payload"]["args"][0]
|
|
63
|
+
job_with_args_eq?(job_class, job_arguments, args)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def job_enqueued_with_args?(job_class, job_arguments, scheduled_job)
|
|
68
|
+
args = scheduled_job["args"][0]
|
|
69
|
+
job_with_args_eq?(job_class, job_arguments, args)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def job_with_args_eq?(job_class, job_arguments, wrapper_args)
|
|
73
|
+
return false if wrapper_args['job_class'] != job_class
|
|
74
|
+
encoded_arguments = wrapper_args['arguments']
|
|
75
|
+
encoded_arguments = job_args(encoded_arguments)
|
|
76
|
+
encoded_arguments == job_arguments
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def job(job)
|
|
80
|
+
job_arguments = ActiveJob::Arguments.serialize(job.arguments)
|
|
81
|
+
job_arguments = job_args(job_arguments)
|
|
82
|
+
job_class = job.class.name
|
|
83
|
+
[ job_class, job_arguments ]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def job_args(args)
|
|
87
|
+
if args.present?
|
|
88
|
+
args.map do |arg|
|
|
89
|
+
if arg.is_a? Hash
|
|
90
|
+
arg.keep_if { |k,v| @only_args.include?(k.to_s) } if @only_args.present?
|
|
91
|
+
arg.keep_if { |k,v| !@except_args.include?(k.to_s) } if @except_args.present?
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
arg
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
args
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_job_resque_solo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Phillip Kinkade
|
|
@@ -106,6 +106,7 @@ files:
|
|
|
106
106
|
- bin/console
|
|
107
107
|
- bin/setup
|
|
108
108
|
- lib/active_job/plugins/resque/solo.rb
|
|
109
|
+
- lib/active_job/plugins/resque/solo/inspector.rb
|
|
109
110
|
- lib/active_job_resque_solo.rb
|
|
110
111
|
- lib/active_job_resque_solo/version.rb
|
|
111
112
|
homepage:
|