sidekiq-unique-jobs 7.0.0.beta27 → 7.0.2

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -14
  3. data/README.md +162 -30
  4. data/lib/sidekiq-unique-jobs.rb +0 -2
  5. data/lib/sidekiq_unique_jobs.rb +1 -0
  6. data/lib/sidekiq_unique_jobs/batch_delete.rb +1 -1
  7. data/lib/sidekiq_unique_jobs/changelog.rb +11 -4
  8. data/lib/sidekiq_unique_jobs/config.rb +2 -2
  9. data/lib/sidekiq_unique_jobs/constants.rb +2 -0
  10. data/lib/sidekiq_unique_jobs/digests.rb +1 -1
  11. data/lib/sidekiq_unique_jobs/job.rb +1 -1
  12. data/lib/sidekiq_unique_jobs/json.rb +7 -1
  13. data/lib/sidekiq_unique_jobs/lock.rb +31 -1
  14. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +1 -1
  15. data/lib/sidekiq_unique_jobs/lock_config.rb +2 -0
  16. data/lib/sidekiq_unique_jobs/locksmith.rb +1 -1
  17. data/lib/sidekiq_unique_jobs/lua/lock.lua +10 -11
  18. data/lib/sidekiq_unique_jobs/lua/reap_orphans.lua +8 -7
  19. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_process_set.lua +9 -2
  20. data/lib/sidekiq_unique_jobs/middleware.rb +0 -57
  21. data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +9 -8
  22. data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +1 -1
  23. data/lib/sidekiq_unique_jobs/orphans/lua_reaper.rb +1 -1
  24. data/lib/sidekiq_unique_jobs/orphans/reaper.rb +10 -0
  25. data/lib/sidekiq_unique_jobs/orphans/ruby_reaper.rb +14 -5
  26. data/lib/sidekiq_unique_jobs/redis/entity.rb +9 -3
  27. data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +27 -0
  28. data/lib/sidekiq_unique_jobs/server.rb +48 -0
  29. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +1 -1
  30. data/lib/sidekiq_unique_jobs/version.rb +1 -1
  31. data/lib/sidekiq_unique_jobs/web.rb +26 -9
  32. data/lib/sidekiq_unique_jobs/web/helpers.rb +24 -3
  33. data/lib/sidekiq_unique_jobs/web/views/changelogs.erb +54 -0
  34. data/lib/sidekiq_unique_jobs/web/views/locks.erb +1 -1
  35. metadata +12 -8
  36. data/lib/sidekiq_unique_jobs/profiler.rb +0 -55
@@ -76,6 +76,16 @@ module SidekiqUniqueJobs
76
76
  config.reaper
77
77
  end
78
78
 
79
+ #
80
+ # The configured timeout for the reaper
81
+ #
82
+ #
83
+ # @return [Integer] timeout in seconds
84
+ #
85
+ def reaper_timeout
86
+ config.reaper_timeout
87
+ end
88
+
79
89
  #
80
90
  # The number of locks to reap at a time
81
91
  #
@@ -52,11 +52,11 @@ module SidekiqUniqueJobs
52
52
  # @return [Array<String>] an array of orphaned digests
53
53
  #
54
54
  def orphans
55
- conn.zrevrange(digests.key, 0, -1).each_with_object([]) do |digest, result|
55
+ conn.zrevrange(digests.key, 0, -1).each_with_object([]) do |digest, memo|
56
56
  next if belongs_to_job?(digest)
57
57
 
58
- result << digest
59
- break if result.size >= reaper_count
58
+ memo << digest
59
+ break if memo.size >= reaper_count
60
60
  end
61
61
  end
62
62
 
@@ -117,7 +117,7 @@ module SidekiqUniqueJobs
117
117
  end
118
118
  end
119
119
 
120
- def active?(digest) # rubocop:disable Metrics/MethodLength
120
+ def active?(digest) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
121
121
  Sidekiq.redis do |conn|
122
122
  procs = conn.sscan_each("processes").to_a
123
123
  return false if procs.empty?
@@ -132,7 +132,12 @@ module SidekiqUniqueJobs
132
132
  next unless workers.any?
133
133
 
134
134
  workers.each_pair do |_tid, job|
135
- return true if load_json(job)[LOCK_DIGEST] == digest
135
+ next unless (item = safe_load_json(job))
136
+
137
+ payload = safe_load_json(item[PAYLOAD])
138
+
139
+ return true if payload[LOCK_DIGEST] == digest
140
+ return true if considered_active?(payload[CREATED_AT])
136
141
  end
137
142
  end
138
143
 
@@ -140,6 +145,10 @@ module SidekiqUniqueJobs
140
145
  end
141
146
  end
142
147
 
148
+ def considered_active?(time_f)
149
+ (Time.now - reaper_timeout).to_f < time_f
150
+ end
151
+
143
152
  #
144
153
  # Loops through all the redis queues and yields them one by one
145
154
  #
@@ -48,10 +48,10 @@ module SidekiqUniqueJobs
48
48
  def exist?
49
49
  redis do |conn|
50
50
  value = conn.exists(key)
51
- return true if value.is_a?(TrueClass)
52
- return false if value.is_a?(FalseClass)
53
51
 
54
- value.positive?
52
+ return value if boolean?(value)
53
+
54
+ value.to_i.positive?
55
55
  end
56
56
  end
57
57
 
@@ -95,6 +95,12 @@ module SidekiqUniqueJobs
95
95
  def count
96
96
  0
97
97
  end
98
+
99
+ private
100
+
101
+ def boolean?(value)
102
+ [TrueClass, FalseClass].any? { |klazz| value.is_a?(klazz) }
103
+ end
98
104
  end
99
105
  end
100
106
  end
@@ -23,6 +23,23 @@ module SidekiqUniqueJobs
23
23
  entrys.each_with_object({}) { |pair, hash| hash[pair[0]] = pair[1] }
24
24
  end
25
25
 
26
+ #
27
+ # Adds a value to the sorted set
28
+ #
29
+ # @param [Array<Float, String>, String] the values to add
30
+ #
31
+ # @return [Boolean, Integer] <description>
32
+ #
33
+ def add(values)
34
+ redis do |conn|
35
+ if values.is_a?(Array)
36
+ conn.zadd(key, values)
37
+ else
38
+ conn.zadd(key, now_f, values)
39
+ end
40
+ end
41
+ end
42
+
26
43
  #
27
44
  # Return the zrak of the member
28
45
  #
@@ -45,6 +62,16 @@ module SidekiqUniqueJobs
45
62
  redis { |conn| conn.zscore(key, member) }
46
63
  end
47
64
 
65
+ #
66
+ # Clears the sorted set from all entries
67
+ #
68
+ #
69
+ # @return [Integer] number of entries removed
70
+ #
71
+ def clear
72
+ redis { |conn| conn.zremrangebyrank(key, 0, count) }
73
+ end
74
+
48
75
  #
49
76
  # Returns the count for this sorted set
50
77
  #
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ # The unique sidekiq middleware for the server processor
5
+ #
6
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
7
+ class Server
8
+ DEATH_HANDLER ||= (lambda do |job, _ex|
9
+ return unless (digest = job["lock_digest"])
10
+
11
+ SidekiqUniqueJobs::Digests.new.delete_by_digest(digest)
12
+ end).freeze
13
+ #
14
+ # Configure the server middleware
15
+ #
16
+ #
17
+ # @return [Sidekiq] the sidekiq configuration
18
+ #
19
+ def self.configure(config)
20
+ config.on(:startup) { start }
21
+ config.on(:shutdown) { stop }
22
+
23
+ return unless config.respond_to?(:death_handlers)
24
+
25
+ config.death_handlers << death_handler
26
+ end
27
+
28
+ def self.start
29
+ SidekiqUniqueJobs::UpdateVersion.call
30
+ SidekiqUniqueJobs::UpgradeLocks.call
31
+ SidekiqUniqueJobs::Orphans::Manager.start
32
+ end
33
+
34
+ def self.stop
35
+ SidekiqUniqueJobs::Orphans::Manager.stop
36
+ end
37
+
38
+ #
39
+ # A death handler for dead jobs
40
+ #
41
+ #
42
+ # @return [lambda]
43
+ #
44
+ def self.death_handler
45
+ DEATH_HANDLER
46
+ end
47
+ end
48
+ end
@@ -168,7 +168,7 @@ module SidekiqUniqueJobs
168
168
  # @option options [Integer] :lock_timeout (default is 0)
169
169
  # @option options [Integer] :lock_ttl (default is 0)
170
170
  # @option options [true,false] :enabled (default is true)
171
- # @option options [String] :unique_prefix (default is 'uniquejobs')
171
+ # @option options [String] :lock_prefix (default is 'uniquejobs')
172
172
  # @option options [Logger] :logger (default is Sidekiq.logger)
173
173
  # @yield control to the caller when given block
174
174
  def configure(options = {})
@@ -3,5 +3,5 @@
3
3
  module SidekiqUniqueJobs
4
4
  #
5
5
  # @return [String] the current SidekiqUniqueJobs version
6
- VERSION = "7.0.0.beta27"
6
+ VERSION = "7.0.2"
7
7
  end
@@ -1,11 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- begin
4
- require "sidekiq/web"
5
- rescue LoadError
6
- # client-only usage
7
- end
8
-
9
3
  require_relative "web/helpers"
10
4
 
11
5
  module SidekiqUniqueJobs
@@ -19,6 +13,23 @@ module SidekiqUniqueJobs
19
13
  include Web::Helpers
20
14
  end
21
15
 
16
+ app.get "/changelogs" do
17
+ @filter = params[:filter] || "*"
18
+ @filter = "*" if @filter == ""
19
+ @count = (params[:count] || 100).to_i
20
+ @current_cursor = params[:cursor]
21
+ @prev_cursor = params[:prev_cursor]
22
+ @pagination = { pattern: @filter, cursor: @current_cursor, page_size: @count }
23
+ @total_size, @next_cursor, @changelogs = changelog.page(**@pagination)
24
+
25
+ erb(unique_template(:changelogs))
26
+ end
27
+
28
+ app.get "/changelogs/delete_all" do
29
+ changelog.clear
30
+ redirect_to :changelogs
31
+ end
32
+
22
33
  app.get "/locks" do
23
34
  @filter = params[:filter] || "*"
24
35
  @filter = "*" if @filter == ""
@@ -59,8 +70,14 @@ module SidekiqUniqueJobs
59
70
  end
60
71
  end
61
72
 
62
- if defined?(Sidekiq::Web)
63
- Sidekiq::Web.register SidekiqUniqueJobs::Web
64
- Sidekiq::Web.tabs["Locks"] = "locks"
73
+ begin
74
+ require "delegate" unless defined?(DelegateClass)
75
+ require "sidekiq/web" unless defined?(Sidekiq::Web)
76
+
77
+ Sidekiq::Web.register(SidekiqUniqueJobs::Web)
78
+ Sidekiq::Web.tabs["Locks"] = "locks"
79
+ Sidekiq::Web.tabs["Changelogs"] = "changelogs"
65
80
  Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), "locales")
81
+ rescue NameError, LoadError => ex
82
+ SidekiqUniqueJobs.logger.error(ex)
66
83
  end
@@ -10,12 +10,12 @@ module SidekiqUniqueJobs
10
10
  module Helpers
11
11
  #
12
12
  # @return [String] the path to gem specific views
13
- VIEW_PATH = File.expand_path("../web/views", __dir__)
13
+ VIEW_PATH = File.expand_path("../web/views", __dir__).freeze
14
14
  #
15
15
  # @return [Array<String>] safe params
16
16
  SAFE_CPARAMS = %w[cursor prev_cursor].freeze
17
17
 
18
- module_function
18
+ extend self
19
19
 
20
20
  #
21
21
  # Opens a template file contained within this gem
@@ -25,7 +25,18 @@ module SidekiqUniqueJobs
25
25
  # @return [String] the file contents of the template
26
26
  #
27
27
  def unique_template(name)
28
- File.open(File.join(VIEW_PATH, "#{name}.erb")).read
28
+ File.open(unique_filename(name)).read
29
+ end
30
+
31
+ #
32
+ # Construct template file name
33
+ #
34
+ # @param [Symbol] name the name of the template
35
+ #
36
+ # @return [String] the full name of the file
37
+ #
38
+ def unique_filename(name)
39
+ File.join(VIEW_PATH, "#{name}.erb")
29
40
  end
30
41
 
31
42
  #
@@ -38,6 +49,16 @@ module SidekiqUniqueJobs
38
49
  @digests ||= SidekiqUniqueJobs::Digests.new
39
50
  end
40
51
 
52
+ #
53
+ # The collection of changelog entries
54
+ #
55
+ #
56
+ # @return [SidekiqUniqueJobs::Digests] the sorted set with digests
57
+ #
58
+ def changelog
59
+ @changelog ||= SidekiqUniqueJobs::Changelog.new
60
+ end
61
+
41
62
  #
42
63
  # Creates url safe parameters
43
64
  #
@@ -0,0 +1,54 @@
1
+ <header class="row">
2
+ <div class="col-sm-5">
3
+ <h3>
4
+ <%= t('Changelog Entries') %>
5
+ </h3>
6
+ </div>
7
+ <form action="<%= root_path %>changelogs" class="form form-inline" method="get">
8
+ <%= csrf_tag %>
9
+ <input name="filter" class="form-control" type="text" value="<%= @filter %>" />
10
+ <button class="btn btn-default" type="submit">
11
+ <%= t('Filter') %>
12
+ </button>
13
+ </form>
14
+ <% 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
+ <% end %>
19
+ </header>
20
+ <% 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>
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>
54
+ <% end %>
@@ -32,7 +32,7 @@
32
32
  <% @locks.each do |lock| %>
33
33
  <tr>
34
34
  <td>
35
- <form action="<%= root_path %>locks/<%= lock.key %>" method="get">
35
+ <form action="<%= root_path %>locks/<%= lock.key %>/delete" method="get">
36
36
  <%= csrf_tag %>
37
37
  <input name="lock" value="<%= h lock.key %>" type="hidden" />
38
38
  <input class="btn btn-danger btn-xs" type="submit" name="delete" value="<%= t('Delete') %>" data-confirm="<%= t('AreYouSure') %>" />
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-unique-jobs
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.0.beta27
4
+ version: 7.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-03 00:00:00.000000000 Z
11
+ date: 2021-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: brpoplpush-redis_script
@@ -56,7 +56,7 @@ dependencies:
56
56
  requirements:
57
57
  - - ">="
58
58
  - !ruby/object:Gem::Version
59
- version: '4.0'
59
+ version: '5.0'
60
60
  - - "<"
61
61
  - !ruby/object:Gem::Version
62
62
  version: '7.0'
@@ -66,7 +66,7 @@ dependencies:
66
66
  requirements:
67
67
  - - ">="
68
68
  - !ruby/object:Gem::Version
69
- version: '4.0'
69
+ version: '5.0'
70
70
  - - "<"
71
71
  - !ruby/object:Gem::Version
72
72
  version: '7.0'
@@ -177,7 +177,6 @@ files:
177
177
  - lib/sidekiq_unique_jobs/orphans/observer.rb
178
178
  - lib/sidekiq_unique_jobs/orphans/reaper.rb
179
179
  - lib/sidekiq_unique_jobs/orphans/ruby_reaper.rb
180
- - lib/sidekiq_unique_jobs/profiler.rb
181
180
  - lib/sidekiq_unique_jobs/redis.rb
182
181
  - lib/sidekiq_unique_jobs/redis/entity.rb
183
182
  - lib/sidekiq_unique_jobs/redis/hash.rb
@@ -189,6 +188,7 @@ files:
189
188
  - lib/sidekiq_unique_jobs/rspec/matchers/have_valid_sidekiq_options.rb
190
189
  - lib/sidekiq_unique_jobs/script.rb
191
190
  - lib/sidekiq_unique_jobs/script/caller.rb
191
+ - lib/sidekiq_unique_jobs/server.rb
192
192
  - lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb
193
193
  - lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb
194
194
  - lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb
@@ -202,6 +202,7 @@ files:
202
202
  - lib/sidekiq_unique_jobs/web.rb
203
203
  - lib/sidekiq_unique_jobs/web/helpers.rb
204
204
  - lib/sidekiq_unique_jobs/web/views/_paging.erb
205
+ - lib/sidekiq_unique_jobs/web/views/changelogs.erb
205
206
  - lib/sidekiq_unique_jobs/web/views/lock.erb
206
207
  - lib/sidekiq_unique_jobs/web/views/locks.erb
207
208
  - lib/tasks/changelog.rake
@@ -217,6 +218,9 @@ metadata:
217
218
  post_install_message: |
218
219
  IMPORTANT!
219
220
 
221
+ Automatic configuration of the sidekiq middelware is no longer done.
222
+ Please see: https://github.com/mhenrixon/sidekiq-unique-jobs/blob/master/README.md#add-the-middleware
223
+
220
224
  This version deprecated the following sidekiq_options
221
225
 
222
226
  - sidekiq_options lock_args: :method_name
@@ -248,11 +252,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
248
252
  version: 2.5.0
249
253
  required_rubygems_version: !ruby/object:Gem::Requirement
250
254
  requirements:
251
- - - ">"
255
+ - - ">="
252
256
  - !ruby/object:Gem::Version
253
- version: 1.3.1
257
+ version: '0'
254
258
  requirements: []
255
- rubygems_version: 3.1.2
259
+ rubygems_version: 3.2.6
256
260
  signing_key:
257
261
  specification_version: 4
258
262
  summary: Sidekiq middleware that prevents duplicates jobs