sidekiq-unique-jobs 7.1.8 → 8.0.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq-unique-jobs might be problematic. Click here for more details.

Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +331 -2
  3. data/README.md +28 -25
  4. data/lib/sidekiq_unique_jobs/batch_delete.rb +7 -10
  5. data/lib/sidekiq_unique_jobs/changelog.rb +7 -17
  6. data/lib/sidekiq_unique_jobs/cli.rb +33 -8
  7. data/lib/sidekiq_unique_jobs/config.rb +5 -0
  8. data/lib/sidekiq_unique_jobs/connection.rb +4 -7
  9. data/lib/sidekiq_unique_jobs/constants.rb +1 -0
  10. data/lib/sidekiq_unique_jobs/core_ext.rb +1 -1
  11. data/lib/sidekiq_unique_jobs/digests.rb +7 -17
  12. data/lib/sidekiq_unique_jobs/exceptions.rb +3 -3
  13. data/lib/sidekiq_unique_jobs/expiring_digests.rb +14 -0
  14. data/lib/sidekiq_unique_jobs/job.rb +6 -1
  15. data/lib/sidekiq_unique_jobs/key.rb +13 -8
  16. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +6 -1
  17. data/lib/sidekiq_unique_jobs/lock/until_executing.rb +4 -0
  18. data/lib/sidekiq_unique_jobs/lock/until_expired.rb +3 -1
  19. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +5 -3
  20. data/lib/sidekiq_unique_jobs/lock.rb +32 -12
  21. data/lib/sidekiq_unique_jobs/lock_args.rb +19 -15
  22. data/lib/sidekiq_unique_jobs/lock_config.rb +6 -6
  23. data/lib/sidekiq_unique_jobs/lock_digest.rb +7 -7
  24. data/lib/sidekiq_unique_jobs/lock_info.rb +2 -2
  25. data/lib/sidekiq_unique_jobs/lock_timeout.rb +4 -4
  26. data/lib/sidekiq_unique_jobs/lock_ttl.rb +4 -4
  27. data/lib/sidekiq_unique_jobs/lock_type.rb +37 -0
  28. data/lib/sidekiq_unique_jobs/locksmith.rb +36 -13
  29. data/lib/sidekiq_unique_jobs/logging.rb +14 -0
  30. data/lib/sidekiq_unique_jobs/lua/delete.lua +3 -6
  31. data/lib/sidekiq_unique_jobs/lua/delete_by_digest.lua +3 -6
  32. data/lib/sidekiq_unique_jobs/lua/delete_job_by_digest.lua +1 -1
  33. data/lib/sidekiq_unique_jobs/lua/find_digest_in_queues.lua +1 -1
  34. data/lib/sidekiq_unique_jobs/lua/lock.lua +16 -10
  35. data/lib/sidekiq_unique_jobs/lua/lock_until_expired.lua +92 -0
  36. data/lib/sidekiq_unique_jobs/lua/locked.lua +1 -1
  37. data/lib/sidekiq_unique_jobs/lua/queue.lua +1 -1
  38. data/lib/sidekiq_unique_jobs/lua/reap_orphans.lua +33 -8
  39. data/lib/sidekiq_unique_jobs/lua/shared/_common.lua +1 -6
  40. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_sorted_set.lua +1 -0
  41. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_process_set.lua +1 -1
  42. data/lib/sidekiq_unique_jobs/lua/unlock.lua +16 -15
  43. data/lib/sidekiq_unique_jobs/lua/update_version.lua +1 -1
  44. data/lib/sidekiq_unique_jobs/lua/upgrade.lua +1 -3
  45. data/lib/sidekiq_unique_jobs/middleware/client.rb +2 -0
  46. data/lib/sidekiq_unique_jobs/middleware/server.rb +2 -0
  47. data/lib/sidekiq_unique_jobs/middleware.rb +4 -4
  48. data/lib/sidekiq_unique_jobs/on_conflict/reject.rb +0 -43
  49. data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +3 -3
  50. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +4 -4
  51. data/lib/sidekiq_unique_jobs/orphans/lua_reaper.rb +1 -1
  52. data/lib/sidekiq_unique_jobs/orphans/manager.rb +6 -13
  53. data/lib/sidekiq_unique_jobs/orphans/ruby_reaper.rb +95 -16
  54. data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +9 -2
  55. data/lib/sidekiq_unique_jobs/redis/string.rb +3 -1
  56. data/lib/sidekiq_unique_jobs/reflections.rb +1 -1
  57. data/lib/sidekiq_unique_jobs/script/caller.rb +14 -8
  58. data/lib/sidekiq_unique_jobs/server.rb +0 -1
  59. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +1 -1
  60. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +14 -4
  61. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +40 -21
  62. data/lib/sidekiq_unique_jobs/testing.rb +53 -21
  63. data/lib/sidekiq_unique_jobs/timer_task.rb +266 -45
  64. data/lib/sidekiq_unique_jobs/timing.rb +1 -1
  65. data/lib/sidekiq_unique_jobs/upgrade_locks.rb +11 -14
  66. data/lib/sidekiq_unique_jobs/version.rb +1 -1
  67. data/lib/sidekiq_unique_jobs/web/helpers.rb +15 -3
  68. data/lib/sidekiq_unique_jobs/web/views/changelogs.erb +44 -38
  69. data/lib/sidekiq_unique_jobs/web/views/lock.erb +5 -3
  70. data/lib/sidekiq_unique_jobs/web/views/locks.erb +42 -37
  71. data/lib/sidekiq_unique_jobs/web.rb +26 -8
  72. data/lib/sidekiq_unique_jobs.rb +2 -0
  73. data/lib/tasks/changelog.rake +1 -1
  74. metadata +16 -43
@@ -21,16 +21,24 @@ module Sidekiq
21
21
  #
22
22
  # @param [Hash<Symbol, Object>] tmp_config the temporary config to use
23
23
  #
24
- def self.use_options(tmp_config = {})
25
- old_options = default_worker_options.dup
24
+ def self.use_options(tmp_config = {}) # rubocop:disable Metrics/MethodLength
25
+ if respond_to?(:default_job_options)
26
+ default_job_options.clear
27
+ self.default_job_options = tmp_config
28
+ else
29
+ default_worker_options.clear
30
+ self.default_worker_options = tmp_config
31
+ end
26
32
 
27
- default_worker_options.clear
28
- self.default_worker_options = tmp_config
29
33
  yield
30
34
  ensure
31
- default_worker_options.clear
32
- self.default_worker_options = DEFAULT_WORKER_OPTIONS
33
- self.default_worker_options = old_options
35
+ if respond_to?(:default_job_options)
36
+ default_job_options.clear
37
+ self.default_job_options = default_job_options
38
+ else
39
+ default_worker_options.clear
40
+ self.default_worker_options = DEFAULT_WORKER_OPTIONS
41
+ end
34
42
  end
35
43
 
36
44
  #
@@ -54,20 +62,14 @@ module Sidekiq
54
62
 
55
63
  yield
56
64
  ensure
57
- self.sidekiq_options_hash = Sidekiq::DEFAULT_WORKER_OPTIONS
58
- sidekiq_options(old_options)
59
- end
65
+ self.sidekiq_options_hash =
66
+ if Sidekiq.respond_to?(:default_job_options)
67
+ Sidekiq.default_job_options
68
+ else
69
+ DEFAULT_WORKER_OPTIONS
70
+ end
60
71
 
61
- #
62
- # Clears the jobs for this worker and removes all locks
63
- #
64
- def clear
65
- jobs.each do |job|
66
- SidekiqUniqueJobs::Unlockable.unlock(job)
67
- end
68
-
69
- Sidekiq::Queues[queue].clear
70
- jobs.clear
72
+ sidekiq_options(old_options)
71
73
  end
72
74
  end
73
75
 
@@ -88,6 +90,36 @@ module Sidekiq
88
90
  super(options)
89
91
  end
90
92
 
93
+ #
94
+ # Prepends deletion of locks to clear
95
+ #
96
+ module ClassMethods
97
+ #
98
+ # Clears the jobs for this worker and removes all locks
99
+ #
100
+ def clear
101
+ jobs.each do |job|
102
+ SidekiqUniqueJobs::Unlockable.unlock(job)
103
+ end
104
+
105
+ super
106
+ end
107
+ end
108
+ end
109
+
110
+ prepend Overrides
111
+
112
+ #
113
+ # Prepends methods to Sidekiq::Worker
114
+ #
115
+ module ClassMethods
116
+ prepend Overrides::ClassMethods
117
+ end
118
+
119
+ #
120
+ # Prepends singleton methods to Sidekiq::Worker
121
+ #
122
+ module SignletonOverrides
91
123
  #
92
124
  # Clears all jobs for this worker and removes all locks
93
125
  #
@@ -98,6 +130,6 @@ module Sidekiq
98
130
  end
99
131
  end
100
132
 
101
- prepend Overrides
133
+ singleton_class.prepend SignletonOverrides
102
134
  end
103
135
  end
@@ -1,25 +1,279 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent/collection/copy_on_notify_observer_set"
4
+ require "concurrent/concern/dereferenceable"
5
+ require "concurrent/concern/observable"
6
+ require "concurrent/atomic/atomic_boolean"
7
+ require "concurrent/executor/executor_service"
8
+ require "concurrent/executor/ruby_executor_service"
9
+ require "concurrent/executor/safe_task_executor"
10
+ require "concurrent/scheduled_task"
11
+
3
12
  module SidekiqUniqueJobs
4
- # @see [Concurrent::TimerTask] https://www.rubydoc.info/gems/concurrent-ruby/Concurrent/TimerTask
13
+ # A very common concurrency pattern is to run a thread that performs a task at
14
+ # regular intervals. The thread that performs the task sleeps for the given
15
+ # interval then wakes up and performs the task. Lather, rinse, repeat... This
16
+ # pattern causes two problems. First, it is difficult to test the business
17
+ # logic of the task because the task itself is tightly coupled with the
18
+ # concurrency logic. Second, an exception raised while performing the task can
19
+ # cause the entire thread to abend. In a long-running application where the
20
+ # task thread is intended to run for days/weeks/years a crashed task thread
21
+ # can pose a significant problem. `TimerTask` alleviates both problems.
22
+ #
23
+ # When a `TimerTask` is launched it starts a thread for monitoring the
24
+ # execution interval. The `TimerTask` thread does not perform the task,
25
+ # however. Instead, the TimerTask launches the task on a separate thread.
26
+ # Should the task experience an unrecoverable crash only the task thread will
27
+ # crash. This makes the `TimerTask` very fault tolerant. Additionally, the
28
+ # `TimerTask` thread can respond to the success or failure of the task,
29
+ # performing logging or ancillary operations.
30
+ #
31
+ # One other advantage of `TimerTask` is that it forces the business logic to
32
+ # be completely decoupled from the concurrency logic. The business logic can
33
+ # be tested separately then passed to the `TimerTask` for scheduling and
34
+ # running.
35
+ #
36
+ # In some cases it may be necessary for a `TimerTask` to affect its own
37
+ # execution cycle. To facilitate this, a reference to the TimerTask instance
38
+ # is passed as an argument to the provided block every time the task is
39
+ # executed.
40
+ #
41
+ # The `TimerTask` class includes the `Dereferenceable` mixin module so the
42
+ # result of the last execution is always available via the `#value` method.
43
+ # Dereferencing options can be passed to the `TimerTask` during construction or
44
+ # at any later time using the `#set_deref_options` method.
45
+ #
46
+ # `TimerTask` supports notification through the Ruby standard library
47
+ # {http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html
48
+ # Observable} module. On execution the `TimerTask` will notify the observers
49
+ # with three arguments: time of execution, the result of the block (or nil on
50
+ # failure), and any raised exceptions (or nil on success).
51
+ #
52
+ # @!macro copy_options
53
+ #
54
+ # @example Basic usage
55
+ # task = Concurrent::TimerTask.new{ puts 'Boom!' }
56
+ # task.execute
57
+ #
58
+ # task.execution_interval #=> 60 (default)
59
+ #
60
+ # # wait 60 seconds...
61
+ # #=> 'Boom!'
62
+ #
63
+ # task.shutdown #=> true
64
+ #
65
+ # @example Configuring `:execution_interval`
66
+ # task = Concurrent::TimerTask.new(execution_interval: 5) do
67
+ # puts 'Boom!'
68
+ # end
69
+ #
70
+ # task.execution_interval #=> 5
71
+ #
72
+ # @example Immediate execution with `:run_now`
73
+ # task = Concurrent::TimerTask.new(run_now: true){ puts 'Boom!' }
74
+ # task.execute
75
+ #
76
+ # #=> 'Boom!'
77
+ #
78
+ # @example Last `#value` and `Dereferenceable` mixin
79
+ # task = Concurrent::TimerTask.new(
80
+ # dup_on_deref: true,
81
+ # execution_interval: 5
82
+ # ){ Time.now }
5
83
  #
6
- class TimerTask < ::Concurrent::TimerTask
84
+ # task.execute
85
+ # Time.now #=> 2013-11-07 18:06:50 -0500
86
+ # sleep(10)
87
+ # task.value #=> 2013-11-07 18:06:55 -0500
88
+ #
89
+ # @example Controlling execution from within the block
90
+ # timer_task = Concurrent::TimerTask.new(execution_interval: 1) do |task|
91
+ # task.execution_interval.times{ print 'Boom! ' }
92
+ # print "\n"
93
+ # task.execution_interval += 1
94
+ # if task.execution_interval > 5
95
+ # puts 'Stopping...'
96
+ # task.shutdown
97
+ # end
98
+ # end
99
+ #
100
+ # timer_task.execute # blocking call - this task will stop itself
101
+ # #=> Boom!
102
+ # #=> Boom! Boom!
103
+ # #=> Boom! Boom! Boom!
104
+ # #=> Boom! Boom! Boom! Boom!
105
+ # #=> Boom! Boom! Boom! Boom! Boom!
106
+ # #=> Stopping...
107
+ #
108
+ # @example Observation
109
+ # class TaskObserver
110
+ # def update(time, result, ex)
111
+ # if result
112
+ # print "(#{time}) Execution successfully returned #{result}\n"
113
+ # else
114
+ # print "(#{time}) Execution failed with error #{ex}\n"
115
+ # end
116
+ # end
117
+ # end
118
+ #
119
+ # task = Concurrent::TimerTask.new(execution_interval: 1){ 42 }
120
+ # task.add_observer(TaskObserver.new)
121
+ # task.execute
122
+ # sleep 4
123
+ #
124
+ # #=> (2013-10-13 19:08:58 -0400) Execution successfully returned 42
125
+ # #=> (2013-10-13 19:08:59 -0400) Execution successfully returned 42
126
+ # #=> (2013-10-13 19:09:00 -0400) Execution successfully returned 42
127
+ # task.shutdown
128
+ #
129
+ # task = Concurrent::TimerTask.new(execution_interval: 1){ sleep }
130
+ # task.add_observer(TaskObserver.new)
131
+ # task.execute
132
+ #
133
+ # #=> (2013-10-13 19:07:25 -0400) Execution timed out
134
+ # #=> (2013-10-13 19:07:27 -0400) Execution timed out
135
+ # #=> (2013-10-13 19:07:29 -0400) Execution timed out
136
+ # task.shutdown
137
+ #
138
+ # task = Concurrent::TimerTask.new(execution_interval: 1){ raise StandardError }
139
+ # task.add_observer(TaskObserver.new)
140
+ # task.execute
141
+ #
142
+ # #=> (2013-10-13 19:09:37 -0400) Execution failed with error StandardError
143
+ # #=> (2013-10-13 19:09:38 -0400) Execution failed with error StandardError
144
+ # #=> (2013-10-13 19:09:39 -0400) Execution failed with error StandardError
145
+ # task.shutdown
146
+ #
147
+ # @see http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html
148
+ # @see http://docs.oracle.com/javase/7/docs/api/java/util/TimerTask.html
149
+ class TimerTask < Concurrent::RubyExecutorService
150
+ include Concurrent::Concern::Dereferenceable
151
+ include Concurrent::Concern::Observable
152
+
153
+ # Default `:execution_interval` in seconds.
154
+ EXECUTION_INTERVAL = 60
155
+
156
+ # Default `:timeout_interval` in seconds.
157
+ TIMEOUT_INTERVAL = 30
158
+
159
+ # Create a new TimerTask with the given task and configuration.
160
+ #
161
+ # @!macro timer_task_initialize
162
+ # @param [Hash] opts the options defining task execution.
163
+ # @option opts [Integer] :execution_interval number of seconds between
164
+ # task executions (default: EXECUTION_INTERVAL)
165
+ # @option opts [Boolean] :run_now Whether to run the task immediately
166
+ # upon instantiation or to wait until the first # execution_interval
167
+ # has passed (default: false)
168
+ #
169
+ # @!macro deref_options
170
+ #
171
+ # @raise ArgumentError when no block is given.
172
+ #
173
+ # @yield to the block after :execution_interval seconds have passed since
174
+ # the last yield
175
+ # @yieldparam task a reference to the `TimerTask` instance so that the
176
+ # block can control its own lifecycle. Necessary since `self` will
177
+ # refer to the execution context of the block rather than the running
178
+ # `TimerTask`.
179
+ #
180
+ # @return [TimerTask] the new `TimerTask`
181
+ def initialize(opts = {}, &task)
182
+ raise ArgumentError, "no block given" unless task
183
+
184
+ super
185
+ set_deref_options opts
186
+ end
187
+
188
+ # Is the executor running?
189
+ #
190
+ # @return [Boolean] `true` when running, `false` when shutting down or shutdown
191
+ def running?
192
+ @running.true?
193
+ end
194
+
195
+ # Execute a previously created `TimerTask`.
196
+ #
197
+ # @return [TimerTask] a reference to `self`
198
+ #
199
+ # @example Instance and execute in separate steps
200
+ # task = Concurrent::TimerTask.new(execution_interval: 10){ print "Hello World\n" }
201
+ # task.running? #=> false
202
+ # task.execute
203
+ # task.running? #=> true
204
+ #
205
+ # @example Instance and execute in one line
206
+ # task = Concurrent::TimerTask.new(execution_interval: 10){ print "Hello World\n" }.execute
207
+ # task.running? #=> true
208
+ def execute
209
+ synchronize do
210
+ if @running.false?
211
+ @running.make_true
212
+ schedule_next_task(@run_now ? 0 : @execution_interval)
213
+ end
214
+ end
215
+ self
216
+ end
217
+
218
+ # Create and execute a new `TimerTask`.
219
+ #
220
+ # @!macro timer_task_initialize
221
+ #
222
+ # @example
223
+ # task = Concurrent::TimerTask.execute(execution_interval: 10){ print "Hello World\n" }
224
+ # task.running? #=> true
225
+ def self.execute(opts = {}, &task)
226
+ TimerTask.new(opts, &task).execute
227
+ end
228
+
229
+ # @!attribute [rw] execution_interval
230
+ # @return [Fixnum] Number of seconds after the task completes before the
231
+ # task is performed again.
232
+ def execution_interval
233
+ synchronize { @execution_interval }
234
+ end
235
+
236
+ # @!attribute [rw] execution_interval
237
+ # @return [Fixnum] Number of seconds after the task completes before the
238
+ # task is performed again.
239
+ def execution_interval=(value)
240
+ raise ArgumentError, "must be greater than zero" if (value = value.to_f) <= 0.0
241
+
242
+ synchronize { @execution_interval = value }
243
+ end
244
+
245
+ private :post, :<<
246
+
7
247
  private
8
248
 
9
249
  def ns_initialize(opts, &task)
10
250
  set_deref_options(opts)
11
251
 
12
252
  self.execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
13
- self.timeout_interval = opts[:timeout] || opts[:timeout_interval] || TIMEOUT_INTERVAL
14
- @run_now = opts[:now] || opts[:run_now]
15
- @executor = Concurrent::RubySingleThreadExecutor.new
16
- @running = Concurrent::AtomicBoolean.new(false)
17
- @task = task
18
- @value = nil
253
+ if opts[:timeout] || opts[:timeout_interval]
254
+ warn "TimeTask timeouts are now ignored as these were not able to be implemented correctly"
255
+ end
256
+ @run_now = opts[:now] || opts[:run_now]
257
+ @executor = Concurrent::SafeTaskExecutor.new(task)
258
+ @running = Concurrent::AtomicBoolean.new(false)
259
+ @value = nil
19
260
 
20
261
  self.observers = Concurrent::Collection::CopyOnNotifyObserverSet.new
21
262
  end
22
263
 
264
+ # @!visibility private
265
+ def ns_shutdown_execution
266
+ @running.make_false
267
+ super
268
+ end
269
+
270
+ # @!visibility private
271
+ def ns_kill_execution
272
+ @running.make_false
273
+ super
274
+ end
275
+
276
+ # @!visibility private
23
277
  def schedule_next_task(interval = execution_interval)
24
278
  exec_task = ->(completion) { execute_task(completion) }
25
279
  Concurrent::ScheduledTask.execute(interval, args: [Concurrent::Event.new], &exec_task)
@@ -27,52 +281,19 @@ module SidekiqUniqueJobs
27
281
  end
28
282
 
29
283
  # @!visibility private
30
- def execute_task(completion) # rubocop:disable Metrics/MethodLength
284
+ def execute_task(completion)
31
285
  return nil unless @running.true?
32
286
 
33
- timeout_task = -> { timeout_task(completion) }
34
-
35
- Concurrent::ScheduledTask.execute(
36
- timeout_interval,
37
- args: [completion],
38
- &timeout_task
39
- )
40
- @thread_completed = Concurrent::Event.new
41
-
42
- @value = @reason = nil
43
- @executor.post do
44
- @value = @task.call(self)
45
- rescue Exception => ex # rubocop:disable Lint/RescueException
46
- @reason = ex
47
- ensure
48
- @thread_completed.set
49
- end
50
-
51
- @thread_completed.wait
52
-
287
+ _success, value, reason = @executor.execute(self)
53
288
  if completion.try?
289
+ self.value = value
54
290
  schedule_next_task
55
291
  time = Time.now
56
292
  observers.notify_observers do
57
- [time, value, @reason]
293
+ [time, self.value, reason]
58
294
  end
59
295
  end
60
296
  nil
61
297
  end
62
-
63
- # @!visibility private
64
- def timeout_task(completion)
65
- return unless @running.true?
66
- return unless completion.try?
67
-
68
- @executor.kill
69
- @executor.wait_for_termination
70
- @executor = Concurrent::RubySingleThreadExecutor.new
71
-
72
- @thread_completed.set
73
-
74
- schedule_next_task
75
- observers.notify_observers(Time.now, nil, Concurrent::TimeoutError.new)
76
- end
77
298
  end
78
299
  end
@@ -48,7 +48,7 @@ module SidekiqUniqueJobs
48
48
  # @return [Float]
49
49
  #
50
50
  def clock_stamp
51
- if Process.const_defined?("CLOCK_MONOTONIC")
51
+ if Process.const_defined?(:CLOCK_MONOTONIC)
52
52
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
53
53
  else
54
54
  now_f
@@ -6,7 +6,7 @@ module SidekiqUniqueJobs
6
6
  #
7
7
  # @author Mikael Henriksson <mikael@mhenrixon.com>
8
8
  #
9
- class UpgradeLocks # rubocop:disable Metrics/ClassLength
9
+ class UpgradeLocks
10
10
  #
11
11
  # @return [Integer] the number of keys to batch upgrade
12
12
  BATCH_SIZE = 100
@@ -56,9 +56,9 @@ module SidekiqUniqueJobs
56
56
 
57
57
  log_info("Start - Upgrading Locks")
58
58
 
59
- upgrade_v6_locks
60
- delete_unused_v6_keys
61
- delete_supporting_v6_keys
59
+ # upgrade_v6_locks
60
+ # delete_unused_v6_keys
61
+ # delete_supporting_v6_keys
62
62
 
63
63
  conn.hset(upgraded_key, version, now_f)
64
64
  log_info("Done - Upgrading Locks")
@@ -75,10 +75,11 @@ module SidekiqUniqueJobs
75
75
 
76
76
  def upgrade_v6_locks
77
77
  log_info("Start - Converting v6 locks to v7")
78
- conn.scan_each(match: "*:GRABBED", count: BATCH_SIZE) do |grabbed_key|
78
+ conn.scan(match: "*:GRABBED", count: BATCH_SIZE).each do |grabbed_key|
79
79
  upgrade_v6_lock(grabbed_key)
80
80
  @count += 1
81
81
  end
82
+
82
83
  log_info("Done - Converting v6 locks to v7")
83
84
  end
84
85
 
@@ -87,9 +88,9 @@ module SidekiqUniqueJobs
87
88
  digest = grabbed_key.gsub(":GRABBED", "")
88
89
  locks = conn.hgetall(grabbed_key)
89
90
 
90
- conn.pipelined do
91
- conn.hmset(locked_key, *locks.to_a)
92
- conn.zadd(DIGESTS, locks.values.first, digest)
91
+ conn.pipelined do |pipeline|
92
+ pipeline.hmset(locked_key, *locks.to_a)
93
+ pipeline.zadd(DIGESTS, locks.values.first, digest)
93
94
  end
94
95
  end
95
96
 
@@ -114,12 +115,8 @@ module SidekiqUniqueJobs
114
115
  def batch_delete(*keys)
115
116
  return if keys.empty?
116
117
 
117
- conn.pipelined do
118
- if VersionCheck.satisfied?(redis_version, ">= 4.0.0")
119
- conn.unlink(*keys)
120
- else
121
- conn.del(*keys)
122
- end
118
+ conn.pipelined do |pipeline|
119
+ pipeline.unlink(*keys)
123
120
  end
124
121
  end
125
122
 
@@ -3,5 +3,5 @@
3
3
  module SidekiqUniqueJobs
4
4
  #
5
5
  # @return [String] the current SidekiqUniqueJobs version
6
- VERSION = "7.1.8"
6
+ VERSION = "8.0.3"
7
7
  end
@@ -27,7 +27,7 @@ module SidekiqUniqueJobs
27
27
  # @return [String] the file contents of the template
28
28
  #
29
29
  def unique_template(name)
30
- File.open(unique_filename(name)).read
30
+ File.read(unique_filename(name))
31
31
  end
32
32
 
33
33
  #
@@ -51,6 +51,16 @@ module SidekiqUniqueJobs
51
51
  @digests ||= SidekiqUniqueJobs::Digests.new
52
52
  end
53
53
 
54
+ #
55
+ # The collection of digests
56
+ #
57
+ #
58
+ # @return [SidekiqUniqueJobs::ExpiringDigests] the sorted set with expiring digests
59
+ #
60
+ def expiring_digests
61
+ @expiring_digests ||= SidekiqUniqueJobs::ExpiringDigests.new
62
+ end
63
+
54
64
  #
55
65
  # The collection of changelog entries
56
66
  #
@@ -70,11 +80,11 @@ module SidekiqUniqueJobs
70
80
  #
71
81
  def cparams(options)
72
82
  stringified_options = options.transform_keys(&:to_s)
73
- params.merge(stringified_options).map do |key, value|
83
+ params.merge(stringified_options).filter_map do |key, value|
74
84
  next unless SAFE_CPARAMS.include?(key)
75
85
 
76
86
  "#{key}=#{CGI.escape(value.to_s)}"
77
- end.compact.join("&")
87
+ end.join("&")
78
88
  end
79
89
 
80
90
  #
@@ -136,6 +146,8 @@ module SidekiqUniqueJobs
136
146
  # @return [String] a html safe string with relative time information
137
147
  #
138
148
  def safe_relative_time(time)
149
+ return unless time
150
+
139
151
  time = parse_time(time)
140
152
 
141
153
  relative_time(time)
@@ -1,54 +1,60 @@
1
1
  <header class="row">
2
2
  <div class="col-sm-5">
3
3
  <h3>
4
- <%= t('Changelog Entries') %>
4
+ <%= t('Changelog Entries') %>
5
5
  </h3>
6
6
  </div>
7
7
  <form action="<%= root_path %>changelogs" class="form form-inline" method="get">
8
8
  <%= csrf_tag %>
9
+
9
10
  <input name="filter" class="form-control" type="text" value="<%= @filter %>" />
11
+
10
12
  <button class="btn btn-default" type="submit">
11
- <%= t('Filter') %>
13
+ <%= t('Filter') %>
12
14
  </button>
13
15
  </form>
16
+
14
17
  <% if @changelogs.any? && @total_size > @count.to_i %>
15
- <div class="col-sm-4">
16
- <%= erb unique_template(:_paging), locals: { url: "#{root_path}changelogs" } %>
17
- </div>
18
+ <div class="col-sm-4">
19
+ <%= erb unique_template(:_paging), locals: { url: "#{root_path}changelogs" } %>
20
+ </div>
18
21
  <% end %>
19
22
  </header>
23
+
20
24
  <% if @changelogs.any? %>
21
- <div class="table_container">
22
- <form action="<%= root_path %>changelogs/delete_all" method="get">
23
- <input class="btn btn-danger btn-xs" type="submit" name="delete_all" value="<%= t('DeleteAll') %>" data-confirm="<%= t('AreYouSure') %>" />
24
- </form>
25
- <br/>
26
- <table class="table table-striped table-bordered table-hover">
27
- <thead>
28
- <tr>
29
- <th><%= t('Time') %></th>
30
- <th><%= t('Digest') %></th>
31
- <th><%= t('Script') %></th>
32
- <th><%= t('JID') %></th>
33
- <th><%= t('Prev JID') %></th>
34
- <th><%= t('Message') %></th>
35
- </tr>
36
- </thead>
37
- <tbody>
38
- <% @changelogs.each do |changelog| %>
39
- <tr class="changelog-row">
40
- <td><%= safe_relative_time(changelog["time"]) %></td>
41
- <td><%= changelog["digest"] %></td>
42
- <td><%= changelog["script"] %></td>
43
- <td><%= changelog["job_id"] %></td>
44
- <td><%= changelog["prev_jid"] %></td>
45
- <td><%= changelog["message"] %></th>
46
- </tr>
47
- <% end %>
48
- </tbody>
49
- </table>
50
- <form action="<%= root_path %>changelogs/delete_all" method="get">
51
- <input class="btn btn-danger btn-xs" type="submit" name="delete_all" value="<%= t('DeleteAll') %>" data-confirm="<%= t('AreYouSure') %>" />
52
- </form>
53
- </div>
25
+ <div class="table_container">
26
+ <form action="<%= root_path %>changelogs/delete_all" method="get">
27
+ <input class="btn btn-danger btn-xs" type="submit" name="delete_all" value="<%= t('DeleteAll') %>" data-confirm="<%= t('AreYouSure') %>" />
28
+ </form>
29
+ <br/>
30
+
31
+ <table class="table table-striped table-bordered table-hover">
32
+ <thead>
33
+ <tr>
34
+ <th><%= t('Time') %></th>
35
+ <th><%= t('Digest') %></th>
36
+ <th><%= t('Script') %></th>
37
+ <th><%= t('JID') %></th>
38
+ <th><%= t('Prev JID') %></th>
39
+ <th><%= t('Message') %></th>
40
+ </tr>
41
+ </thead>
42
+ <tbody>
43
+ <% @changelogs.each do |changelog| %>
44
+ <tr class="changelog-row">
45
+ <td><%= safe_relative_time(changelog['time']) || "bogus" %></td>
46
+ <td><%= changelog["digest"] %></td>
47
+ <td><%= changelog["script"] %></td>
48
+ <td><%= changelog["job_id"] %></td>
49
+ <td><%= changelog["prev_jid"] %></td>
50
+ <td><%= changelog["message"] %></th>
51
+ </tr>
52
+ <% end %>
53
+ </tbody>
54
+ </table>
55
+
56
+ <form action="<%= root_path %>changelogs/delete_all" method="get">
57
+ <input class="btn btn-danger btn-xs" type="submit" name="delete_all" value="<%= t('DeleteAll') %>" data-confirm="<%= t('AreYouSure') %>" />
58
+ </form>
59
+ </div>
54
60
  <% end %>