sidekiq-middleware 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ dump.rdb
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 if not provided
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
- expiration = (payload['at'].to_i - Time.now.to_i)
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 = "locks:unique:#{Digest::MD5.hexdigest(Sidekiq.dump_json(payload))}"
21
+ payload_hash = Sidekiq::Middleware::UniqueKey.generate(worker_class, payload)
21
22
 
22
23
  Sidekiq.redis do |conn|
23
24
  conn.watch(payload_hash)
@@ -11,4 +11,4 @@ Sidekiq.configure_client do |config|
11
11
  config.client_middleware do |chain|
12
12
  chain.add Sidekiq::Middleware::Client::UniqueJobs
13
13
  end
14
- end
14
+ end
@@ -4,31 +4,31 @@ module Sidekiq
4
4
  class UniqueJobs
5
5
 
6
6
  def call(worker_instance, item, queue)
7
- forever = worker_instance.class.get_sidekiq_options['forever']
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 forever
12
+ clear(worker_instance, item, queue) unless manual
17
13
  end
18
14
  end
19
15
 
20
- def clear(worker_instance, item, queue)
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.redis { |conn| conn.del "locks:unique:#{Digest::MD5.hexdigest(Sidekiq.dump_json(payload))}" }
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
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  module Middleware
3
- VERSION = "0.0.6"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
  end
@@ -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
@@ -12,6 +12,8 @@ require 'celluloid'
12
12
  Celluloid.logger = nil
13
13
 
14
14
  require 'sidekiq'
15
+ require 'sidekiq/cli'
16
+ require 'sidekiq/processor'
15
17
  require 'sidekiq/util'
16
18
  require 'sidekiq-middleware'
17
19
  Sidekiq.logger.level = Logger::ERROR
@@ -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, forever: true
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
- it 'allows the job to reschedule itself with enabled forever option' do
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' => UniqueScheduledWorker.to_s, 'args' => ['something'])
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
- @processor.process(msg, 'default')
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' => UniqueScheduledWorker.to_s, 'args' => ['something'], 'sent_at' => (Time.now + i*60).to_f)
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
- @processor.process(msg, 'default')
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.6
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-01-07 00:00:00.000000000 Z
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