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 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