sidekiq-middleware 0.0.6 → 0.1.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.
- data/.gitignore +1 -0
- data/CHANGES.md +8 -2
- data/README.md +42 -4
- data/lib/sidekiq-middleware/client/unique_jobs.rb +3 -2
- data/lib/sidekiq-middleware/middleware.rb +1 -1
- data/lib/sidekiq-middleware/server/unique_jobs.rb +9 -9
- data/lib/sidekiq-middleware/unique_key.rb +14 -0
- data/lib/sidekiq-middleware/version.rb +1 -1
- data/lib/sidekiq-middleware.rb +2 -1
- data/test/helper.rb +2 -0
- data/test/test_unique_jobs.rb +60 -7
- metadata +9 -2
data/.gitignore
CHANGED
data/CHANGES.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
0.1.0
|
2
|
+
-----------
|
3
|
+
|
4
|
+
- Added ability to set custom lock key ([dimko](https://github.com/dimko))
|
5
|
+
- Removed forever option due to race condition issues. Added ability to manually operate unique locks instead
|
6
|
+
|
1
7
|
0.0.6
|
2
8
|
-----------
|
3
9
|
|
@@ -11,12 +17,12 @@
|
|
11
17
|
0.0.4
|
12
18
|
-----------
|
13
19
|
|
14
|
-
- Fixed Hash#slice (bnorton)
|
20
|
+
- Fixed Hash#slice ([bnorton](https://github.com/bnorton))
|
15
21
|
|
16
22
|
0.0.3
|
17
23
|
-----------
|
18
24
|
|
19
|
-
- Refactored and simplified the UniqueJobs middleware server and client as well as only enforcing the uniqueness of the payload across the keys for class, queue, args, and at (bnorton)
|
25
|
+
- Refactored and simplified the UniqueJobs middleware server and client as well as only enforcing the uniqueness of the payload across the keys for class, queue, args, and at ([bnorton](https://github.com/bnorton))
|
20
26
|
|
21
27
|
0.0.2
|
22
28
|
-----------
|
data/README.md
CHANGED
@@ -37,11 +37,8 @@ class UniqueWorker
|
|
37
37
|
# or :all (enables uniqueness for both async and scheduled jobs)
|
38
38
|
unique: :all,
|
39
39
|
|
40
|
-
# Set this to true in case your job schedules itself
|
41
|
-
forever: true,
|
42
|
-
|
43
40
|
# Unique expiration (optional, default is 30 minutes)
|
44
|
-
# For scheduled jobs calculates automatically
|
41
|
+
# For scheduled jobs calculates automatically
|
45
42
|
expiration: 24 * 60 * 60
|
46
43
|
})
|
47
44
|
|
@@ -51,6 +48,47 @@ class UniqueWorker
|
|
51
48
|
end
|
52
49
|
```
|
53
50
|
|
51
|
+
Custom lock key and manual expiration:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class UniqueWorker
|
55
|
+
include Sidekiq::Worker
|
56
|
+
|
57
|
+
sidekiq_options({
|
58
|
+
unique: :all,
|
59
|
+
expiration: 24 * 60 * 60,
|
60
|
+
|
61
|
+
# Set this to true when you need to handle locks manually.
|
62
|
+
# You'll be able to handle unique expiration inside your worker.
|
63
|
+
# Please see example below.
|
64
|
+
manual: true
|
65
|
+
})
|
66
|
+
|
67
|
+
# Implement your own lock string
|
68
|
+
def self.lock(id)
|
69
|
+
"locks:unique:#{id}"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Implement method to handle lock removing manually
|
73
|
+
def self.unlock!(id)
|
74
|
+
lock = self.lock(id)
|
75
|
+
Sidekiq.redis { |conn| conn.del(lock) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def perform(id)
|
79
|
+
# Your code goes here
|
80
|
+
# You are able to re-schedule job from perform method,
|
81
|
+
# Just remove lock manually before performing job again.
|
82
|
+
sleep 5
|
83
|
+
|
84
|
+
# Re-schedule!
|
85
|
+
self.class.unlock!(id)
|
86
|
+
self.class.perform_async(id)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
|
54
92
|
## Contributing
|
55
93
|
|
56
94
|
1. Fork it
|
@@ -13,11 +13,12 @@ module Sidekiq
|
|
13
13
|
|
14
14
|
# Enabled unique scheduled
|
15
15
|
if enabled == :all && payload.has_key?('at')
|
16
|
-
|
16
|
+
scheduled_expiration = (payload['at'].to_i - Time.now.to_i)
|
17
|
+
expiration = scheduled_expiration if scheduled_expiration > expiration
|
17
18
|
payload.delete('at')
|
18
19
|
end
|
19
20
|
|
20
|
-
payload_hash =
|
21
|
+
payload_hash = Sidekiq::Middleware::UniqueKey.generate(worker_class, payload)
|
21
22
|
|
22
23
|
Sidekiq.redis do |conn|
|
23
24
|
conn.watch(payload_hash)
|
@@ -4,31 +4,31 @@ module Sidekiq
|
|
4
4
|
class UniqueJobs
|
5
5
|
|
6
6
|
def call(worker_instance, item, queue)
|
7
|
-
|
8
|
-
|
9
|
-
# Delete lock first if forever is set
|
10
|
-
# Used for jobs which may scheduling self in future
|
11
|
-
clear(worker_instance, item, queue) if forever
|
7
|
+
manual = worker_instance.class.get_sidekiq_options['manual']
|
12
8
|
|
13
9
|
begin
|
14
10
|
yield
|
15
11
|
ensure
|
16
|
-
clear(worker_instance, item, queue) unless
|
12
|
+
clear(worker_instance, item, queue) unless manual
|
17
13
|
end
|
18
14
|
end
|
19
15
|
|
20
|
-
def
|
16
|
+
def unique_lock_key(worker_instance, item, queue)
|
21
17
|
# Only enforce uniqueness across class, queue, args, and at.
|
22
18
|
# Useful when middleware uses the payload to store metadata.
|
23
19
|
enabled, payload = worker_instance.class.get_sidekiq_options['unique'],
|
24
20
|
item.clone.slice(*%w(class queue args at))
|
25
21
|
|
26
|
-
# Enabled unique scheduled
|
22
|
+
# Enabled unique scheduled
|
27
23
|
if enabled == :all && payload.has_key?('at')
|
28
24
|
payload.delete('at')
|
29
25
|
end
|
30
26
|
|
31
|
-
Sidekiq
|
27
|
+
Sidekiq::Middleware::UniqueKey.generate(worker_instance.class, payload)
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear(worker_instance, item, queue)
|
31
|
+
Sidekiq.redis { |conn| conn.del unique_lock_key(worker_instance, item, queue) }
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Middleware
|
3
|
+
module UniqueKey
|
4
|
+
def self.generate(worker_class, payload)
|
5
|
+
if worker_class.respond_to?(:lock)
|
6
|
+
worker_class.lock(*payload['args'])
|
7
|
+
else
|
8
|
+
json = Sidekiq.dump_json(payload)
|
9
|
+
"locks:unique:#{Digest::MD5.hexdigest(json)}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/sidekiq-middleware.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "digest"
|
2
2
|
require "sidekiq-middleware/version"
|
3
3
|
require "sidekiq-middleware/core_ext"
|
4
|
+
require "sidekiq-middleware/unique_key"
|
4
5
|
require "sidekiq-middleware/server/unique_jobs"
|
5
6
|
require "sidekiq-middleware/client/unique_jobs"
|
6
|
-
require "sidekiq-middleware/middleware"
|
7
|
+
require "sidekiq-middleware/middleware"
|
data/test/helper.rb
CHANGED
data/test/test_unique_jobs.rb
CHANGED
@@ -10,11 +10,25 @@ class TestUniqueJobs < MiniTest::Unit::TestCase
|
|
10
10
|
before do
|
11
11
|
@boss = MiniTest::Mock.new
|
12
12
|
@processor = ::Sidekiq::Processor.new(@boss)
|
13
|
-
|
13
|
+
|
14
14
|
Sidekiq.redis = REDIS
|
15
15
|
Sidekiq.redis {|c| c.flushdb }
|
16
16
|
end
|
17
17
|
|
18
|
+
UnitOfWork = Struct.new(:queue, :message) do
|
19
|
+
def acknowledge
|
20
|
+
# nothing to do
|
21
|
+
end
|
22
|
+
|
23
|
+
def queue_name
|
24
|
+
queue
|
25
|
+
end
|
26
|
+
|
27
|
+
def requeue
|
28
|
+
# nothing to do
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
18
32
|
class UniqueWorker
|
19
33
|
include Sidekiq::Worker
|
20
34
|
sidekiq_options queue: :unique_queue, unique: true
|
@@ -48,7 +62,7 @@ class TestUniqueJobs < MiniTest::Unit::TestCase
|
|
48
62
|
|
49
63
|
class UniqueScheduledWorker
|
50
64
|
include Sidekiq::Worker
|
51
|
-
sidekiq_options queue: :unique_scheduled_queue, unique: :all,
|
65
|
+
sidekiq_options queue: :unique_scheduled_queue, unique: :all, manual: true
|
52
66
|
|
53
67
|
def perform(x)
|
54
68
|
UniqueScheduledWorker.perform_in(60, x)
|
@@ -60,26 +74,65 @@ class TestUniqueJobs < MiniTest::Unit::TestCase
|
|
60
74
|
assert_equal 1, Sidekiq.redis { |c| c.zcard('schedule') }
|
61
75
|
end
|
62
76
|
|
63
|
-
|
77
|
+
class CustomUniqueWorker
|
78
|
+
include Sidekiq::Worker
|
79
|
+
sidekiq_options queue: :custom_unique_queue, unique: :all, manual: true
|
80
|
+
|
81
|
+
def self.lock(id, unlock)
|
82
|
+
"custom:unique:lock:#{id}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.unlock!(id, unlock)
|
86
|
+
lock = self.lock(id, unlock)
|
87
|
+
Sidekiq.redis { |conn| conn.del(lock) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def perform(id, unlock)
|
91
|
+
self.class.unlock!(id, unlock) if unlock
|
92
|
+
CustomUniqueWorker.perform_in(60, id, unlock)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'does not duplicate messages with enabled unique option and custom unique lock key' do
|
97
|
+
5.times { CustomUniqueWorker.perform_async('args', false) }
|
98
|
+
assert_equal 1, Sidekiq.redis { |c| c.llen('queue:custom_unique_queue') }
|
99
|
+
assert_equal 1, Sidekiq.redis { |c| c.get('custom:unique:lock:args').to_i }
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'does not allow the job to be duplicated when processing job with manual option' do
|
64
103
|
5.times {
|
65
|
-
msg = Sidekiq.dump_json('class' =>
|
104
|
+
msg = Sidekiq.dump_json('class' => CustomUniqueWorker.to_s, 'args' => ['something', false])
|
66
105
|
actor = MiniTest::Mock.new
|
67
106
|
actor.expect(:processor_done, nil, [@processor])
|
68
107
|
@boss.expect(:async, actor, [])
|
69
|
-
|
108
|
+
work = UnitOfWork.new('default', msg)
|
109
|
+
@processor.process(work)
|
70
110
|
}
|
71
111
|
assert_equal 1, Sidekiq.redis { |c| c.zcard('schedule') }
|
72
112
|
end
|
73
113
|
|
74
114
|
it 'discards non critical information about the message' do
|
75
115
|
5.times {|i|
|
76
|
-
msg = Sidekiq.dump_json('class' =>
|
116
|
+
msg = Sidekiq.dump_json('class' => CustomUniqueWorker.to_s, 'args' => ['something', false], 'sent_at' => (Time.now + i*60).to_f)
|
77
117
|
actor = MiniTest::Mock.new
|
78
118
|
actor.expect(:processor_done, nil, [@processor])
|
79
119
|
@boss.expect(:async, actor, [])
|
80
|
-
|
120
|
+
work = UnitOfWork.new('default', msg)
|
121
|
+
@processor.process(work)
|
81
122
|
}
|
82
123
|
assert_equal 1, Sidekiq.redis { |c| c.zcard('schedule') }
|
83
124
|
end
|
125
|
+
|
126
|
+
it 'allows a job to be rescheduled when processing using unlock' do
|
127
|
+
5.times {
|
128
|
+
msg = Sidekiq.dump_json('class' => CustomUniqueWorker.to_s, 'args' => ['something', true])
|
129
|
+
actor = MiniTest::Mock.new
|
130
|
+
actor.expect(:processor_done, nil, [@processor])
|
131
|
+
@boss.expect(:async, actor, [])
|
132
|
+
work = UnitOfWork.new('default', msg)
|
133
|
+
@processor.process(work)
|
134
|
+
}
|
135
|
+
assert_equal 5, Sidekiq.redis { |c| c.zcard('schedule') }
|
136
|
+
end
|
84
137
|
end
|
85
138
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-middleware
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-05-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sidekiq
|
@@ -78,6 +78,7 @@ files:
|
|
78
78
|
- lib/sidekiq-middleware/core_ext.rb
|
79
79
|
- lib/sidekiq-middleware/middleware.rb
|
80
80
|
- lib/sidekiq-middleware/server/unique_jobs.rb
|
81
|
+
- lib/sidekiq-middleware/unique_key.rb
|
81
82
|
- lib/sidekiq-middleware/version.rb
|
82
83
|
- sidekiq-middleware.gemspec
|
83
84
|
- test/helper.rb
|
@@ -95,12 +96,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
95
96
|
- - ! '>='
|
96
97
|
- !ruby/object:Gem::Version
|
97
98
|
version: '0'
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
hash: 1078771584105605648
|
98
102
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
103
|
none: false
|
100
104
|
requirements:
|
101
105
|
- - ! '>='
|
102
106
|
- !ruby/object:Gem::Version
|
103
107
|
version: '0'
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
hash: 1078771584105605648
|
104
111
|
requirements: []
|
105
112
|
rubyforge_project:
|
106
113
|
rubygems_version: 1.8.24
|