sidekiq-unique-jobs 3.0.11 → 8.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +2163 -25
  3. data/LICENSE.txt +21 -0
  4. data/README.md +984 -47
  5. data/bin/uniquejobs +7 -0
  6. data/lib/sidekiq-unique-jobs.rb +2 -36
  7. data/lib/sidekiq_unique_jobs/batch_delete.rb +120 -0
  8. data/lib/sidekiq_unique_jobs/changelog.rb +68 -0
  9. data/lib/sidekiq_unique_jobs/cli.rb +95 -0
  10. data/lib/sidekiq_unique_jobs/config.rb +306 -33
  11. data/lib/sidekiq_unique_jobs/connection.rb +20 -0
  12. data/lib/sidekiq_unique_jobs/constants.rb +55 -0
  13. data/lib/sidekiq_unique_jobs/core_ext.rb +132 -0
  14. data/lib/sidekiq_unique_jobs/deprecation.rb +65 -0
  15. data/lib/sidekiq_unique_jobs/digests.rb +134 -0
  16. data/lib/sidekiq_unique_jobs/exceptions.rb +105 -0
  17. data/lib/sidekiq_unique_jobs/expiring_digests.rb +14 -0
  18. data/lib/sidekiq_unique_jobs/job.rb +63 -0
  19. data/lib/sidekiq_unique_jobs/json.rb +47 -0
  20. data/lib/sidekiq_unique_jobs/key.rb +98 -0
  21. data/lib/sidekiq_unique_jobs/lock/base_lock.rb +165 -0
  22. data/lib/sidekiq_unique_jobs/lock/client_validator.rb +28 -0
  23. data/lib/sidekiq_unique_jobs/lock/server_validator.rb +27 -0
  24. data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +71 -0
  25. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +48 -0
  26. data/lib/sidekiq_unique_jobs/lock/until_executing.rb +43 -0
  27. data/lib/sidekiq_unique_jobs/lock/until_expired.rb +42 -0
  28. data/lib/sidekiq_unique_jobs/lock/validator.rb +96 -0
  29. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +70 -0
  30. data/lib/sidekiq_unique_jobs/lock/while_executing_reject.rb +21 -0
  31. data/lib/sidekiq_unique_jobs/lock.rb +348 -0
  32. data/lib/sidekiq_unique_jobs/lock_args.rb +127 -0
  33. data/lib/sidekiq_unique_jobs/lock_config.rb +132 -0
  34. data/lib/sidekiq_unique_jobs/lock_digest.rb +79 -0
  35. data/lib/sidekiq_unique_jobs/lock_info.rb +68 -0
  36. data/lib/sidekiq_unique_jobs/lock_timeout.rb +62 -0
  37. data/lib/sidekiq_unique_jobs/lock_ttl.rb +77 -0
  38. data/lib/sidekiq_unique_jobs/lock_type.rb +37 -0
  39. data/lib/sidekiq_unique_jobs/locksmith.rb +390 -0
  40. data/lib/sidekiq_unique_jobs/logging/middleware_context.rb +44 -0
  41. data/lib/sidekiq_unique_jobs/logging.rb +236 -0
  42. data/lib/sidekiq_unique_jobs/lua/delete.lua +49 -0
  43. data/lib/sidekiq_unique_jobs/lua/delete_by_digest.lua +39 -0
  44. data/lib/sidekiq_unique_jobs/lua/delete_job_by_digest.lua +38 -0
  45. data/lib/sidekiq_unique_jobs/lua/find_digest_in_queues.lua +26 -0
  46. data/lib/sidekiq_unique_jobs/lua/lock.lua +108 -0
  47. data/lib/sidekiq_unique_jobs/lua/lock_until_expired.lua +92 -0
  48. data/lib/sidekiq_unique_jobs/lua/locked.lua +35 -0
  49. data/lib/sidekiq_unique_jobs/lua/queue.lua +88 -0
  50. data/lib/sidekiq_unique_jobs/lua/reap_orphans.lua +119 -0
  51. data/lib/sidekiq_unique_jobs/lua/shared/_common.lua +35 -0
  52. data/lib/sidekiq_unique_jobs/lua/shared/_current_time.lua +8 -0
  53. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_queue.lua +22 -0
  54. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_sorted_set.lua +29 -0
  55. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_process_set.lua +53 -0
  56. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_queues.lua +43 -0
  57. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_sorted_set.lua +24 -0
  58. data/lib/sidekiq_unique_jobs/lua/shared/_hgetall.lua +13 -0
  59. data/lib/sidekiq_unique_jobs/lua/shared/_upgrades.lua +3 -0
  60. data/lib/sidekiq_unique_jobs/lua/unlock.lua +112 -0
  61. data/lib/sidekiq_unique_jobs/lua/update_version.lua +40 -0
  62. data/lib/sidekiq_unique_jobs/lua/upgrade.lua +66 -0
  63. data/lib/sidekiq_unique_jobs/middleware/client.rb +42 -0
  64. data/lib/sidekiq_unique_jobs/middleware/server.rb +31 -0
  65. data/lib/sidekiq_unique_jobs/middleware.rb +41 -15
  66. data/lib/sidekiq_unique_jobs/normalizer.rb +17 -0
  67. data/lib/sidekiq_unique_jobs/on_conflict/log.rb +24 -0
  68. data/lib/sidekiq_unique_jobs/on_conflict/null_strategy.rb +16 -0
  69. data/lib/sidekiq_unique_jobs/on_conflict/raise.rb +17 -0
  70. data/lib/sidekiq_unique_jobs/on_conflict/reject.rb +75 -0
  71. data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +82 -0
  72. data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +39 -0
  73. data/lib/sidekiq_unique_jobs/on_conflict/strategy.rb +51 -0
  74. data/lib/sidekiq_unique_jobs/on_conflict.rb +44 -0
  75. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +78 -0
  76. data/lib/sidekiq_unique_jobs/orphans/lua_reaper.rb +29 -0
  77. data/lib/sidekiq_unique_jobs/orphans/manager.rb +242 -0
  78. data/lib/sidekiq_unique_jobs/orphans/null_reaper.rb +24 -0
  79. data/lib/sidekiq_unique_jobs/orphans/observer.rb +42 -0
  80. data/lib/sidekiq_unique_jobs/orphans/reaper.rb +115 -0
  81. data/lib/sidekiq_unique_jobs/orphans/reaper_resurrector.rb +170 -0
  82. data/lib/sidekiq_unique_jobs/orphans/ruby_reaper.rb +313 -0
  83. data/lib/sidekiq_unique_jobs/redis/entity.rb +112 -0
  84. data/lib/sidekiq_unique_jobs/redis/hash.rb +56 -0
  85. data/lib/sidekiq_unique_jobs/redis/list.rb +32 -0
  86. data/lib/sidekiq_unique_jobs/redis/set.rb +32 -0
  87. data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +102 -0
  88. data/lib/sidekiq_unique_jobs/redis/string.rb +51 -0
  89. data/lib/sidekiq_unique_jobs/redis.rb +11 -0
  90. data/lib/sidekiq_unique_jobs/reflectable.rb +26 -0
  91. data/lib/sidekiq_unique_jobs/reflections.rb +79 -0
  92. data/lib/sidekiq_unique_jobs/rspec/matchers/have_valid_sidekiq_options.rb +51 -0
  93. data/lib/sidekiq_unique_jobs/rspec/matchers.rb +26 -0
  94. data/lib/sidekiq_unique_jobs/script/caller.rb +133 -0
  95. data/lib/sidekiq_unique_jobs/script/client.rb +94 -0
  96. data/lib/sidekiq_unique_jobs/script/config.rb +68 -0
  97. data/lib/sidekiq_unique_jobs/script/dsl.rb +60 -0
  98. data/lib/sidekiq_unique_jobs/script/logging.rb +95 -0
  99. data/lib/sidekiq_unique_jobs/script/lua_error.rb +96 -0
  100. data/lib/sidekiq_unique_jobs/script/script.rb +75 -0
  101. data/lib/sidekiq_unique_jobs/script/scripts.rb +123 -0
  102. data/lib/sidekiq_unique_jobs/script/template.rb +41 -0
  103. data/lib/sidekiq_unique_jobs/script/timing.rb +35 -0
  104. data/lib/sidekiq_unique_jobs/script.rb +46 -0
  105. data/lib/sidekiq_unique_jobs/server.rb +62 -0
  106. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +110 -37
  107. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +304 -0
  108. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +84 -0
  109. data/lib/sidekiq_unique_jobs/testing.rb +132 -9
  110. data/lib/sidekiq_unique_jobs/timer_task.rb +299 -0
  111. data/lib/sidekiq_unique_jobs/timing.rb +58 -0
  112. data/lib/sidekiq_unique_jobs/unlockable.rb +43 -0
  113. data/lib/sidekiq_unique_jobs/update_version.rb +25 -0
  114. data/lib/sidekiq_unique_jobs/upgrade_locks.rb +152 -0
  115. data/lib/sidekiq_unique_jobs/version.rb +5 -1
  116. data/lib/sidekiq_unique_jobs/version_check.rb +114 -0
  117. data/lib/sidekiq_unique_jobs/web/helpers.rb +175 -0
  118. data/lib/sidekiq_unique_jobs/web/views/_paging.erb +10 -0
  119. data/lib/sidekiq_unique_jobs/web/views/changelogs.erb +60 -0
  120. data/lib/sidekiq_unique_jobs/web/views/lock.erb +110 -0
  121. data/lib/sidekiq_unique_jobs/web/views/locks.erb +59 -0
  122. data/lib/sidekiq_unique_jobs/web.rb +109 -0
  123. data/lib/sidekiq_unique_jobs.rb +83 -0
  124. data/lib/tasks/changelog.rake +23 -0
  125. metadata +157 -126
  126. data/.gitignore +0 -10
  127. data/.rspec +0 -3
  128. data/.rubocop.yml +0 -36
  129. data/.travis.yml +0 -25
  130. data/Appraisals +0 -20
  131. data/Gemfile +0 -5
  132. data/LICENSE +0 -22
  133. data/Rakefile +0 -11
  134. data/gemfiles/sidekiq_2.15.gemfile +0 -9
  135. data/gemfiles/sidekiq_2.16.gemfile +0 -9
  136. data/gemfiles/sidekiq_2.17.gemfile +0 -9
  137. data/gemfiles/sidekiq_3.0.gemfile +0 -9
  138. data/gemfiles/sidekiq_develop.gemfile +0 -9
  139. data/lib/sidekiq_unique_jobs/connectors/redis_pool.rb +0 -11
  140. data/lib/sidekiq_unique_jobs/connectors/sidekiq_redis.rb +0 -9
  141. data/lib/sidekiq_unique_jobs/connectors/testing.rb +0 -11
  142. data/lib/sidekiq_unique_jobs/connectors.rb +0 -16
  143. data/lib/sidekiq_unique_jobs/middleware/client/strategies/testing_inline.rb +0 -25
  144. data/lib/sidekiq_unique_jobs/middleware/client/strategies/unique.rb +0 -76
  145. data/lib/sidekiq_unique_jobs/middleware/client/unique_jobs.rb +0 -39
  146. data/lib/sidekiq_unique_jobs/middleware/server/unique_jobs.rb +0 -69
  147. data/lib/sidekiq_unique_jobs/payload_helper.rb +0 -42
  148. data/sidekiq-unique-jobs.gemspec +0 -27
  149. data/spec/lib/.sidekiq_testing_enabled_spec.rb.swp +0 -0
  150. data/spec/lib/client_spec.rb +0 -173
  151. data/spec/lib/middleware/server/unique_jobs_spec.rb +0 -81
  152. data/spec/lib/sidekiq_testing_enabled_spec.rb +0 -123
  153. data/spec/lib/sidekiq_unique_ext_spec.rb +0 -70
  154. data/spec/lib/unlock_order_spec.rb +0 -64
  155. data/spec/spec_helper.rb +0 -37
  156. data/spec/support/my_worker.rb +0 -13
  157. data/spec/support/sidekiq_meta.rb +0 -17
  158. data/spec/support/unique_worker.rb +0 -13
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ module Web
5
+ #
6
+ # Provides view helpers for the Sidekiq::Web extension
7
+ #
8
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
9
+ #
10
+ module Helpers
11
+ #
12
+ # @return [String] the path to gem specific views
13
+ VIEW_PATH = File.expand_path("../web/views", __dir__).freeze
14
+ #
15
+ # @return [Array<String>] safe params
16
+ SAFE_CPARAMS = %w[
17
+ filter count cursor prev_cursor poll direction
18
+ ].freeze
19
+
20
+ extend self
21
+
22
+ #
23
+ # Opens a template file contained within this gem
24
+ #
25
+ # @param [Symbol] name the name of the template
26
+ #
27
+ # @return [String] the file contents of the template
28
+ #
29
+ def unique_template(name)
30
+ File.read(unique_filename(name))
31
+ end
32
+
33
+ #
34
+ # Construct template file name
35
+ #
36
+ # @param [Symbol] name the name of the template
37
+ #
38
+ # @return [String] the full name of the file
39
+ #
40
+ def unique_filename(name)
41
+ File.join(VIEW_PATH, "#{name}.erb")
42
+ end
43
+
44
+ #
45
+ # The collection of digests
46
+ #
47
+ #
48
+ # @return [SidekiqUniqueJobs::Digests] the sorted set with digests
49
+ #
50
+ def digests
51
+ @digests ||= SidekiqUniqueJobs::Digests.new
52
+ end
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
+
64
+ #
65
+ # The collection of changelog entries
66
+ #
67
+ #
68
+ # @return [SidekiqUniqueJobs::Digests] the sorted set with digests
69
+ #
70
+ def changelog
71
+ @changelog ||= SidekiqUniqueJobs::Changelog.new
72
+ end
73
+
74
+ #
75
+ # Creates url safe parameters
76
+ #
77
+ # @param [Hash] options the key/value to parameterize
78
+ #
79
+ # @return [String] a url safe parameter string
80
+ #
81
+ def cparams(options)
82
+ stringified_options = options.transform_keys(&:to_s)
83
+ params.merge(stringified_options).filter_map do |key, value|
84
+ next unless SAFE_CPARAMS.include?(key)
85
+
86
+ "#{key}=#{CGI.escape(value.to_s)}"
87
+ end.join("&")
88
+ end
89
+
90
+ #
91
+ # Used to avoid incompatibility with older sidekiq versions
92
+ #
93
+ #
94
+ # @param [Array] args the unique arguments to display
95
+ # @param [Integer] truncate_after_chars
96
+ #
97
+ # @return [String] a string containing all non-truncated arguments
98
+ #
99
+ def display_lock_args(args, truncate_after_chars = 2000)
100
+ return "Invalid job payload, args is nil" if args.nil?
101
+ return "Invalid job payload, args must be an Array, not #{args.class.name}" unless args.is_a?(Array)
102
+
103
+ begin
104
+ args.map do |arg|
105
+ h(truncate(to_display(arg), truncate_after_chars))
106
+ end.join(", ")
107
+ rescue StandardError
108
+ "Illegal job arguments: #{h args.inspect}"
109
+ end
110
+ end
111
+
112
+ #
113
+ # Redirect to with falback
114
+ #
115
+ # @param [String] subpath the path to redirect to
116
+ #
117
+ # @return a redirect to the new subpath
118
+ #
119
+ def redirect_to(subpath)
120
+ if respond_to?(:to)
121
+ # Sinatra-based web UI
122
+ redirect to(subpath)
123
+ else
124
+ # Non-Sinatra based web UI (Sidekiq 4.2+)
125
+ redirect "#{root_path}#{subpath}"
126
+ end
127
+ end
128
+
129
+ #
130
+ # Gets a relative time as html
131
+ #
132
+ # @param [Time] time an instance of Time
133
+ #
134
+ # @return [String] a html safe string with relative time information
135
+ #
136
+ def _relative_time(time)
137
+ stamp = time.getutc.iso8601
138
+ %(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
139
+ end
140
+
141
+ #
142
+ # Gets a relative time as html without crashing
143
+ #
144
+ # @param [Float, Integer, String, Time] time a representation of a timestamp
145
+ #
146
+ # @return [String] a html safe string with relative time information
147
+ #
148
+ def _safe_relative_time(time)
149
+ return unless time
150
+
151
+ time = parse_time(time)
152
+
153
+ _relative_time(time)
154
+ end
155
+
156
+ #
157
+ # Constructs a time from a number of different types
158
+ #
159
+ # @param [Float, Integer, String, Time] time a representation of a timestamp
160
+ #
161
+ # @return [Time]
162
+ #
163
+ def parse_time(time)
164
+ case time
165
+ when Time
166
+ time
167
+ when Integer, Float
168
+ Time.at(time)
169
+ else
170
+ Time.parse(time.to_s)
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,10 @@
1
+ <ul class="pagination pull-right flip">
2
+ <% if @prev_cursor %>
3
+ <li>
4
+ <a href="<%= url %>?<%= cparams(filter: @filter, cursor: @prev_cursor, prev_cursor: @next_cursor) %>">Previous <%= @count %></a>
5
+ </li>
6
+ <% end %>
7
+ <li>
8
+ <a href="<%= url %>?<%= cparams(filter: @filter, cursor: @next_cursor, prev_cursor: @current_cursor) %>">Next <%= @count %></a>
9
+ </li>
10
+ </ul>
@@ -0,0 +1,60 @@
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
+
10
+ <input name="filter" class="form-control" type="text" value="<%= @filter %>" />
11
+
12
+ <button class="btn btn-default" type="submit">
13
+ <%= t('Filter') %>
14
+ </button>
15
+ </form>
16
+
17
+ <% if @changelogs.any? && @total_size > @count.to_i %>
18
+ <div class="col-sm-4">
19
+ <%= erb unique_template(:_paging), locals: { url: "#{root_path}changelogs" } %>
20
+ </div>
21
+ <% end %>
22
+ </header>
23
+
24
+ <% if @changelogs.any? %>
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>
60
+ <% end %>
@@ -0,0 +1,110 @@
1
+ <header class="row">
2
+ <div class="col-sm-5">
3
+ <h3>
4
+ <%= t('Lock information') %> <a class="btn btn-default btn-xs" href="<%= root_path %>locks"><%= t('GoBack') %></a>
5
+ </h3>
6
+ </div>
7
+ <div class="col-sm-7 table-responsive">
8
+ <% if @lock.info.none? %>
9
+ <h3>No Lock Information Available</h3>
10
+ <% unless SidekiqUniqueJobs.config.lock_info %>
11
+ <p>To use it turn the following setting on:
12
+ <code>SidekiqUniqueJobs.config.lock_info = true</code>
13
+ </p>
14
+ <% end %>
15
+ <% else %>
16
+ <table class="table table-striped table-bordered table-white table-hover">
17
+ <caption>Information about lock</caption>
18
+ <tbody>
19
+ <tr>
20
+ <th scope=row><%= t('Worker') %></td>
21
+ <td><%= @lock.info["worker"] %></td>
22
+ </tr>
23
+ <tr>
24
+ <th scope=row><%= t('Queue') %></td>
25
+ <td><%= @lock.info["queue"] %></td>
26
+ </tr>
27
+ <tr>
28
+ <th scope=row><%= t('Limit') %></td>
29
+ <td><%= @lock.info["limit"] %></td>
30
+ </tr>
31
+ <tr>
32
+ <th scope=row><%= t('TTL') %></td>
33
+ <td><%= @lock.info["ttl"] %></td>
34
+ </tr>
35
+ <tr>
36
+ <th scope=row><%= t('Timeout') %></td>
37
+ <td><%= @lock.info["timeout"] %></td>
38
+ </tr>
39
+ <tr>
40
+ <th scope=row><%= t('Args') %></td>
41
+ <td>
42
+ <code class="code-wrap">
43
+ <!-- We don't want to truncate any job arguments when viewing a single job's status page -->
44
+ <div class="args-extended"><%= display_lock_args(@lock.info["lock_args"], nil) %></div>
45
+ </code>
46
+ </td>
47
+ </tr>
48
+ </tbody>
49
+ </table>
50
+ <% end %>
51
+ </div>
52
+ </header>
53
+ <div class="row">
54
+ <div class="col-sm-6 table-responsive">
55
+ <table class="table table-striped table-bordered table-hover">
56
+ <caption>Digest: <strong class="text-muted"><%= @lock.key %></strong></caption>
57
+ <thead>
58
+ <tr>
59
+ <th scope="col"><%= t('Locked JIDs') %></th>
60
+ <th scope="col"><%= t('Since') %></th>
61
+ <th scope="col"></th>
62
+ </tr>
63
+ </thead>
64
+ <tbody>
65
+ <% @lock.locked_jids(with_values: true).each do |job_id, time| %>
66
+ <tr>
67
+ <td><%= job_id %></td>
68
+ <td><%= _safe_relative_time(time.to_f) %></td>
69
+ <td>
70
+ <form action="<%= root_path %>locks/<%= @lock.key %>/jobs/<%= job_id %>/delete" method="get">
71
+ <%= csrf_tag %>
72
+ <input class="btn btn-danger btn-xs flip" type="submit" name="delete" value="<%= t('Unlock') %>" data-confirm="<%= t('AreYouSure') %>" />
73
+ </form>
74
+ </td>
75
+ </tr>
76
+ <% end %>
77
+ </tbody>
78
+ </table>
79
+ </div>
80
+ <div class="col-sm-6 table-responsive">
81
+ <% if @lock.changelog.count.positive? %>
82
+ <table class="table table-striped table-bordered table-hover">
83
+ <caption>Changelogs</caption>
84
+ <thead>
85
+ <tr>
86
+ <th scope="col"><%= t('At') %></th>
87
+ <th scope="col"><%= t('JID') %></th>
88
+ <th scope="col"><%= t('Message') %></th>
89
+ <th scope="col"><%= t('Script') %></th>
90
+ </tr>
91
+ </thead>
92
+ <tbody>
93
+ <% @lock.changelogs.each do |entry| %>
94
+ <tr>
95
+ <td scope="row"><%= _safe_relative_time(entry["time"].to_f) %></td>
96
+ <td><%= entry["job_id"] %></td>
97
+ <td><%= entry["message"] %></td>
98
+ <td><%= entry["script"] %></td>
99
+ </tr>
100
+ <% end %>
101
+ </tbody>
102
+ </table>
103
+ <% end %>
104
+ </div>
105
+ </div>
106
+ <form action="<%= root_path %>locks/<%= @lock.key %>/delete" method="get">
107
+ <%= csrf_tag %>
108
+ <a class="btn btn-default btn-xs" href="<%= root_path %>locks"><%= t('GoBack') %></a>
109
+ <input class="btn btn-danger btn-xs flip" type="submit" name="delete" value="<%= t('Delete') %>" data-confirm="<%= t('AreYouSure') %>" />
110
+ </form>
@@ -0,0 +1,59 @@
1
+ <header class="row">
2
+ <div class="col-sm-5">
3
+ <h3>
4
+ <%= t('Locks') %>
5
+ </h3>
6
+ </div>
7
+ <form action="<%= root_path %>locks" class="form form-inline" method="get">
8
+ <%= csrf_tag %>
9
+ <input name="filter" class="form-control" type="text" value="<%= @filter %>" />
10
+
11
+ <button class="btn btn-default" type="submit">
12
+ <%= t('Filter') %>
13
+ </button>
14
+
15
+ </form>
16
+
17
+ <% if @locks.any? && @total_size > @count %>
18
+ <div class="col-sm-4">
19
+ <%= erb unique_template(:_paging), locals: { url: "#{root_path}locks" } %>
20
+ </div>
21
+ <% end %>
22
+ </header>
23
+
24
+ <% if @locks.any? %>
25
+ <div class="table_container">
26
+ <table class="table table-striped table-bordered table-hover">
27
+ <thead>
28
+ <tr>
29
+ <th><%= t('Delete') %></th>
30
+ <th><%= t('Digest') %></th>
31
+ <th><%= t('Lock') %></th>
32
+ <th><%= t('Locks') %></th>
33
+ <th><%= t('Since') %></th>
34
+ </tr>
35
+ </thead>
36
+ <% @locks.each do |lock| %>
37
+ <tbody>
38
+ <tr class="lock-row">
39
+ <td>
40
+ <form action="<%= root_path %>locks/<%= lock.key %>/delete" method="get">
41
+ <%= csrf_tag %>
42
+ <input name="lock" value="<%= h lock.key %>" type="hidden" />
43
+ <input class="btn btn-danger btn-xs" type="submit" name="delete" value="<%= t('Delete') %>" data-confirm="<%= t('AreYouSure') %>" />
44
+ </form>
45
+ </td>
46
+ <td><a href="<%= root_path %>locks/<%= lock.key %>"><%= lock.key %></a></td>
47
+ <td><%= lock.info["lock"] %></td>
48
+ <td><%= lock.locked.count %></td>
49
+ <td><%= _safe_relative_time(lock.created_at) %></td>
50
+ </tr>
51
+ </tbody>
52
+ <% end %>
53
+ </table>
54
+
55
+ <form action="<%= root_path %>locks/delete_all" method="get">
56
+ <input class="btn btn-danger btn-xs" type="submit" name="delete_all" value="<%= t('DeleteAll') %>" data-confirm="<%= t('AreYouSure') %>" />
57
+ </form>
58
+ </div>
59
+ <% end %>
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "web/helpers"
4
+
5
+ module SidekiqUniqueJobs
6
+ # Utility module to help manage unique keys in redis.
7
+ # Useful for deleting keys that for whatever reason wasn't deleted
8
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
9
+ module Web
10
+ def self.registered(app) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
11
+ app.helpers do
12
+ include Web::Helpers
13
+ end
14
+
15
+ app.get "/changelogs" do
16
+ @filter = h(params[:filter] || "*")
17
+ @filter = "*" if @filter == ""
18
+ @count = h(params[:count] || 100).to_i
19
+ @current_cursor = h(params[:cursor]).to_i
20
+ @prev_cursor = h(params[:prev_cursor]).to_i
21
+ @total_size, @next_cursor, @changelogs = changelog.page(
22
+ cursor: @current_cursor,
23
+ pattern: @filter,
24
+ page_size: @count,
25
+ )
26
+
27
+ erb(unique_template(:changelogs))
28
+ end
29
+
30
+ app.get "/changelogs/delete_all" do
31
+ changelog.clear
32
+ redirect_to :changelogs
33
+ end
34
+
35
+ app.get "/locks" do
36
+ @filter = h(params[:filter]) || "*"
37
+ @filter = "*" if @filter == ""
38
+ @count = h(params[:count] || 100).to_i
39
+ @current_cursor = h(params[:cursor]).to_i
40
+ @prev_cursor = h(params[:prev_cursor]).to_i
41
+
42
+ @total_size, @next_cursor, @locks = digests.page(
43
+ cursor: @current_cursor,
44
+ pattern: @filter,
45
+ page_size: @count,
46
+ )
47
+
48
+ erb(unique_template(:locks))
49
+ end
50
+
51
+ app.get "/expiring_locks" do
52
+ @filter = h(params[:filter]) || "*"
53
+ @filter = "*" if @filter == ""
54
+ @count = h(params[:count] || 100).to_i
55
+ @current_cursor = h(params[:cursor]).to_i
56
+ @prev_cursor = h(params[:prev_cursor]).to_i
57
+
58
+ @total_size, @next_cursor, @locks = expiring_digests.page(
59
+ cursor: @current_cursor,
60
+ pattern: @filter,
61
+ page_size: @count,
62
+ )
63
+
64
+ erb(unique_template(:locks))
65
+ end
66
+
67
+ app.get "/locks/delete_all" do
68
+ digests.delete_by_pattern("*", count: digests.count)
69
+ expiring_digests.delete_by_pattern("*", count: digests.count)
70
+ redirect_to :locks
71
+ end
72
+
73
+ app.get "/locks/:digest" do
74
+ @digest = h(params[:digest])
75
+ @lock = SidekiqUniqueJobs::Lock.new(@digest)
76
+
77
+ erb(unique_template(:lock))
78
+ end
79
+
80
+ app.get "/locks/:digest/delete" do
81
+ digests.delete_by_digest(h(params[:digest]))
82
+ expiring_digests.delete_by_digest(h(params[:digest]))
83
+ redirect_to :locks
84
+ end
85
+
86
+ app.get "/locks/:digest/jobs/:job_id/delete" do
87
+ @digest = h(params[:digest])
88
+ @job_id = h(params[:job_id])
89
+ @lock = SidekiqUniqueJobs::Lock.new(@digest)
90
+ @lock.unlock(@job_id)
91
+
92
+ redirect_to "locks/#{@lock.key}"
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ begin
99
+ require "delegate" unless defined?(DelegateClass)
100
+ require "sidekiq/web" unless defined?(Sidekiq::Web)
101
+
102
+ Sidekiq::Web.register(SidekiqUniqueJobs::Web)
103
+ Sidekiq::Web.tabs["Locks"] = "locks"
104
+ Sidekiq::Web.tabs["Expiring Locks"] = "expiring_locks"
105
+ Sidekiq::Web.tabs["Changelogs"] = "changelogs"
106
+ Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), "locales")
107
+ rescue NameError, LoadError => ex
108
+ SidekiqUniqueJobs.logger.error(ex)
109
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/executor/ruby_single_thread_executor"
4
+ require "concurrent/future"
5
+ require "concurrent/map"
6
+ require "concurrent/mutable_struct"
7
+ require "concurrent/promises"
8
+ require "concurrent/timer_task"
9
+ require "digest"
10
+ require "digest/sha1"
11
+ require "erb"
12
+ require "forwardable"
13
+ require "json"
14
+ require "pathname"
15
+ require "redis_client"
16
+ require "sidekiq"
17
+
18
+ require "sidekiq_unique_jobs/script"
19
+
20
+ require "sidekiq_unique_jobs/deprecation"
21
+ require "sidekiq_unique_jobs/reflections"
22
+ require "sidekiq_unique_jobs/reflectable"
23
+ require "sidekiq_unique_jobs/timer_task"
24
+ require "sidekiq_unique_jobs/version"
25
+ require "sidekiq_unique_jobs/version_check"
26
+ require "sidekiq_unique_jobs/constants"
27
+ require "sidekiq_unique_jobs/json"
28
+ require "sidekiq_unique_jobs/logging"
29
+ require "sidekiq_unique_jobs/logging/middleware_context"
30
+ require "sidekiq_unique_jobs/timing"
31
+ require "sidekiq_unique_jobs/sidekiq_worker_methods"
32
+ require "sidekiq_unique_jobs/connection"
33
+ require "sidekiq_unique_jobs/exceptions"
34
+ require "sidekiq_unique_jobs/script/caller"
35
+ require "sidekiq_unique_jobs/normalizer"
36
+ require "sidekiq_unique_jobs/job"
37
+ require "sidekiq_unique_jobs/redis"
38
+ require "sidekiq_unique_jobs/redis/entity"
39
+ require "sidekiq_unique_jobs/redis/hash"
40
+ require "sidekiq_unique_jobs/redis/list"
41
+ require "sidekiq_unique_jobs/redis/set"
42
+ require "sidekiq_unique_jobs/redis/sorted_set"
43
+ require "sidekiq_unique_jobs/redis/string"
44
+ require "sidekiq_unique_jobs/batch_delete"
45
+ require "sidekiq_unique_jobs/orphans/reaper"
46
+ require "sidekiq_unique_jobs/orphans/observer"
47
+ require "sidekiq_unique_jobs/orphans/manager"
48
+ require "sidekiq_unique_jobs/orphans/reaper_resurrector"
49
+ require "sidekiq_unique_jobs/cli"
50
+ require "sidekiq_unique_jobs/core_ext"
51
+ require "sidekiq_unique_jobs/lock_timeout"
52
+ require "sidekiq_unique_jobs/lock_ttl"
53
+ require "sidekiq_unique_jobs/lock_type"
54
+ require "sidekiq_unique_jobs/lock_args"
55
+ require "sidekiq_unique_jobs/lock_digest"
56
+ require "sidekiq_unique_jobs/unlockable"
57
+ require "sidekiq_unique_jobs/key"
58
+ require "sidekiq_unique_jobs/locksmith"
59
+ require "sidekiq_unique_jobs/options_with_fallback"
60
+ require "sidekiq_unique_jobs/lock"
61
+ require "sidekiq_unique_jobs/lock_config"
62
+ require "sidekiq_unique_jobs/lock_info"
63
+ require "sidekiq_unique_jobs/lock/base_lock"
64
+ require "sidekiq_unique_jobs/lock/until_executed"
65
+ require "sidekiq_unique_jobs/lock/until_executing"
66
+ require "sidekiq_unique_jobs/lock/until_expired"
67
+ require "sidekiq_unique_jobs/lock/while_executing"
68
+ require "sidekiq_unique_jobs/lock/while_executing_reject"
69
+ require "sidekiq_unique_jobs/lock/until_and_while_executing"
70
+ require "sidekiq_unique_jobs/middleware"
71
+ require "sidekiq_unique_jobs/middleware/client"
72
+ require "sidekiq_unique_jobs/middleware/server"
73
+ require "sidekiq_unique_jobs/sidekiq_unique_ext"
74
+ require "sidekiq_unique_jobs/on_conflict"
75
+ require "sidekiq_unique_jobs/changelog"
76
+ require "sidekiq_unique_jobs/digests"
77
+ require "sidekiq_unique_jobs/expiring_digests"
78
+
79
+ require "sidekiq_unique_jobs/config"
80
+ require "sidekiq_unique_jobs/sidekiq_unique_jobs"
81
+ require "sidekiq_unique_jobs/update_version"
82
+ require "sidekiq_unique_jobs/upgrade_locks"
83
+ require "sidekiq_unique_jobs/server"
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Style/MutableConstant
4
+ CHANGELOG_CMD = %w[
5
+ github_changelog_generator
6
+ --no-verbose
7
+ --user
8
+ mhenrixon
9
+ --project
10
+ sidekiq-unique-jobs
11
+ --token
12
+ ]
13
+ ADD_CHANGELOG_CMD = "git add --all"
14
+ COMMIT_CHANGELOG_CMD = "git commit -a -m 'Update changelog'"
15
+ # rubocop:enable Style/MutableConstant
16
+
17
+ desc "Generate a Changelog"
18
+ task :changelog do
19
+ sh("git checkout main")
20
+ sh(*CHANGELOG_CMD.push(ENV.fetch("CHANGELOG_GITHUB_TOKEN", nil)))
21
+ sh(ADD_CHANGELOG_CMD)
22
+ sh(COMMIT_CHANGELOG_CMD)
23
+ end