sidekiq_cleaner 5.3.6

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.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +61 -0
  3. data/.github/contributing.md +32 -0
  4. data/.github/issue_template.md +11 -0
  5. data/.gitignore +15 -0
  6. data/.travis.yml +11 -0
  7. data/3.0-Upgrade.md +70 -0
  8. data/4.0-Upgrade.md +53 -0
  9. data/5.0-Upgrade.md +56 -0
  10. data/COMM-LICENSE +97 -0
  11. data/Changes.md +1536 -0
  12. data/Ent-Changes.md +238 -0
  13. data/Gemfile +23 -0
  14. data/LICENSE +9 -0
  15. data/Pro-2.0-Upgrade.md +138 -0
  16. data/Pro-3.0-Upgrade.md +44 -0
  17. data/Pro-4.0-Upgrade.md +35 -0
  18. data/Pro-Changes.md +759 -0
  19. data/README.md +55 -0
  20. data/Rakefile +9 -0
  21. data/bin/sidekiq +18 -0
  22. data/bin/sidekiqctl +20 -0
  23. data/bin/sidekiqload +149 -0
  24. data/cleaner/assets/images/favicon.ico +0 -0
  25. data/cleaner/assets/images/logo.png +0 -0
  26. data/cleaner/assets/images/status.png +0 -0
  27. data/cleaner/assets/javascripts/application.js +172 -0
  28. data/cleaner/assets/javascripts/dashboard.js +315 -0
  29. data/cleaner/assets/stylesheets/application-rtl.css +246 -0
  30. data/cleaner/assets/stylesheets/application.css +1144 -0
  31. data/cleaner/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  32. data/cleaner/assets/stylesheets/bootstrap.css +5 -0
  33. data/cleaner/locales/ar.yml +81 -0
  34. data/cleaner/locales/cs.yml +78 -0
  35. data/cleaner/locales/da.yml +68 -0
  36. data/cleaner/locales/de.yml +69 -0
  37. data/cleaner/locales/el.yml +68 -0
  38. data/cleaner/locales/en.yml +81 -0
  39. data/cleaner/locales/es.yml +70 -0
  40. data/cleaner/locales/fa.yml +80 -0
  41. data/cleaner/locales/fr.yml +78 -0
  42. data/cleaner/locales/he.yml +79 -0
  43. data/cleaner/locales/hi.yml +75 -0
  44. data/cleaner/locales/it.yml +69 -0
  45. data/cleaner/locales/ja.yml +80 -0
  46. data/cleaner/locales/ko.yml +68 -0
  47. data/cleaner/locales/nb.yml +77 -0
  48. data/cleaner/locales/nl.yml +68 -0
  49. data/cleaner/locales/pl.yml +59 -0
  50. data/cleaner/locales/pt-br.yml +68 -0
  51. data/cleaner/locales/pt.yml +67 -0
  52. data/cleaner/locales/ru.yml +78 -0
  53. data/cleaner/locales/sv.yml +68 -0
  54. data/cleaner/locales/ta.yml +75 -0
  55. data/cleaner/locales/uk.yml +76 -0
  56. data/cleaner/locales/ur.yml +80 -0
  57. data/cleaner/locales/zh-cn.yml +68 -0
  58. data/cleaner/locales/zh-tw.yml +68 -0
  59. data/cleaner/views/_footer.erb +20 -0
  60. data/cleaner/views/_job_info.erb +88 -0
  61. data/cleaner/views/_nav.erb +52 -0
  62. data/cleaner/views/_paging.erb +23 -0
  63. data/cleaner/views/_poll_link.erb +7 -0
  64. data/cleaner/views/_status.erb +4 -0
  65. data/cleaner/views/_summary.erb +40 -0
  66. data/cleaner/views/busy.erb +98 -0
  67. data/cleaner/views/dashboard.erb +75 -0
  68. data/cleaner/views/dead.erb +34 -0
  69. data/cleaner/views/errors.erb +84 -0
  70. data/cleaner/views/layout.erb +40 -0
  71. data/cleaner/views/morgue.erb +75 -0
  72. data/cleaner/views/queue.erb +46 -0
  73. data/cleaner/views/queues.erb +30 -0
  74. data/cleaner/views/retries.erb +80 -0
  75. data/cleaner/views/retry.erb +34 -0
  76. data/cleaner/views/scheduled.erb +54 -0
  77. data/cleaner/views/scheduled_job_info.erb +8 -0
  78. data/cleaner-stats.png +0 -0
  79. data/cleaner.png +0 -0
  80. data/code_of_conduct.md +50 -0
  81. data/lib/generators/sidekiq/templates/worker.rb.erb +9 -0
  82. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +6 -0
  83. data/lib/generators/sidekiq/templates/worker_test.rb.erb +8 -0
  84. data/lib/generators/sidekiq/worker_generator.rb +49 -0
  85. data/lib/sidekiq/api.rb +940 -0
  86. data/lib/sidekiq/cleaner/action.rb +89 -0
  87. data/lib/sidekiq/cleaner/application.rb +385 -0
  88. data/lib/sidekiq/cleaner/helpers.rb +325 -0
  89. data/lib/sidekiq/cleaner/router.rb +100 -0
  90. data/lib/sidekiq/cleaner.rb +214 -0
  91. data/lib/sidekiq/cli.rb +445 -0
  92. data/lib/sidekiq/client.rb +243 -0
  93. data/lib/sidekiq/core_ext.rb +1 -0
  94. data/lib/sidekiq/ctl.rb +221 -0
  95. data/lib/sidekiq/delay.rb +42 -0
  96. data/lib/sidekiq/exception_handler.rb +29 -0
  97. data/lib/sidekiq/extensions/action_mailer.rb +57 -0
  98. data/lib/sidekiq/extensions/active_record.rb +40 -0
  99. data/lib/sidekiq/extensions/class_methods.rb +40 -0
  100. data/lib/sidekiq/extensions/generic_proxy.rb +31 -0
  101. data/lib/sidekiq/fetch.rb +81 -0
  102. data/lib/sidekiq/job_logger.rb +25 -0
  103. data/lib/sidekiq/job_retry.rb +262 -0
  104. data/lib/sidekiq/launcher.rb +173 -0
  105. data/lib/sidekiq/logging.rb +122 -0
  106. data/lib/sidekiq/manager.rb +137 -0
  107. data/lib/sidekiq/middleware/chain.rb +150 -0
  108. data/lib/sidekiq/middleware/i18n.rb +42 -0
  109. data/lib/sidekiq/middleware/server/active_record.rb +23 -0
  110. data/lib/sidekiq/paginator.rb +43 -0
  111. data/lib/sidekiq/processor.rb +279 -0
  112. data/lib/sidekiq/rails.rb +58 -0
  113. data/lib/sidekiq/redis_connection.rb +144 -0
  114. data/lib/sidekiq/scheduled.rb +174 -0
  115. data/lib/sidekiq/testing/inline.rb +29 -0
  116. data/lib/sidekiq/testing.rb +333 -0
  117. data/lib/sidekiq/util.rb +66 -0
  118. data/lib/sidekiq/version.rb +4 -0
  119. data/lib/sidekiq/worker.rb +220 -0
  120. data/lib/sidekiq.rb +237 -0
  121. data/sidekiq_cleaner.gemspec +21 -0
  122. metadata +235 -0
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ class CleanerAction
5
+ RACK_SESSION = 'rack.session'
6
+
7
+ attr_accessor :env, :block, :type
8
+
9
+ def settings
10
+ Cleaner.settings
11
+ end
12
+
13
+ def request
14
+ @request ||= ::Rack::Request.new(env)
15
+ end
16
+
17
+ def halt(res)
18
+ throw :halt, res
19
+ end
20
+
21
+ def redirect(location)
22
+ throw :halt, [302, { "Location" => "#{request.base_url}#{location}" }, []]
23
+ end
24
+
25
+ def params
26
+ indifferent_hash = Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
27
+
28
+ indifferent_hash.merge! request.params
29
+ route_params.each {|k,v| indifferent_hash[k.to_s] = v }
30
+
31
+ indifferent_hash
32
+ end
33
+
34
+ def route_params
35
+ env[CleanerRouter::ROUTE_PARAMS]
36
+ end
37
+
38
+ def session
39
+ env[RACK_SESSION]
40
+ end
41
+
42
+ def erb(content, options = {})
43
+ if content.kind_of? Symbol
44
+ unless respond_to?(:"_erb_#{content}")
45
+ src = ERB.new(File.read("#{Cleaner.settings.views}/#{content}.erb")).src
46
+ CleanerAction.class_eval("def _erb_#{content}\n#{src}\n end")
47
+ end
48
+ end
49
+
50
+ if @_erb
51
+ _erb(content, options[:locals])
52
+ else
53
+ @_erb = true
54
+ content = _erb(content, options[:locals])
55
+
56
+ _render { content }
57
+ end
58
+ end
59
+
60
+ def render(engine, content, options = {})
61
+ raise "Only erb templates are supported" if engine != :erb
62
+
63
+ erb(content, options)
64
+ end
65
+
66
+ def json(payload)
67
+ [200, { "Content-Type" => "application/json", "Cache-Control" => "no-cache" }, [Sidekiq.dump_json(payload)]]
68
+ end
69
+
70
+ def initialize(env, block)
71
+ @_erb = false
72
+ @env = env
73
+ @block = block
74
+ @@files ||= {}
75
+ end
76
+
77
+ private
78
+
79
+ def _erb(file, locals)
80
+ locals.each {|k, v| define_singleton_method(k){ v } unless (singleton_methods.include? k)} if locals
81
+
82
+ if file.kind_of?(String)
83
+ ERB.new(file).result(binding)
84
+ else
85
+ send(:"_erb_#{file}")
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,385 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ class CleanerApplication
5
+ extend CleanerRouter
6
+
7
+ CONTENT_LENGTH = "Content-Length"
8
+ CONTENT_TYPE = "Content-Type"
9
+ REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human)
10
+ CSP_HEADER = [
11
+ "default-src 'self' https: http:",
12
+ "child-src 'self'",
13
+ "connect-src 'self' https: http: wss: ws:",
14
+ "font-src 'self' https: http:",
15
+ "frame-src 'self'",
16
+ "img-src 'self' https: http: data:",
17
+ "manifest-src 'self'",
18
+ "media-src 'self'",
19
+ "object-src 'none'",
20
+ "script-src 'self' https: http: 'unsafe-inline'",
21
+ "style-src 'self' https: http: 'unsafe-inline'",
22
+ "worker-src 'self'",
23
+ "base-uri 'self'"
24
+ ].join('; ').freeze
25
+
26
+ def initialize(klass)
27
+ @klass = klass
28
+ end
29
+
30
+ def settings
31
+ @klass.settings
32
+ end
33
+
34
+ def self.settings
35
+ Sidekiq::Cleaner.settings
36
+ end
37
+
38
+ def self.tabs
39
+ Sidekiq::Cleaner.tabs
40
+ end
41
+
42
+ def self.set(key, val)
43
+ # nothing, backwards compatibility
44
+ end
45
+
46
+ get "/" do
47
+ @redis_info = redis_info.select{ |k, v| REDIS_KEYS.include? k }
48
+ stats_history = Sidekiq::Stats::History.new((params['days'] || 30).to_i)
49
+ @processed_history = stats_history.processed
50
+ @failed_history = stats_history.failed
51
+
52
+ erb(:dashboard)
53
+ end
54
+
55
+ get "/busy" do
56
+ erb(:busy)
57
+ end
58
+
59
+ post "/busy" do
60
+ if params['identity']
61
+ p = Sidekiq::Process.new('identity' => params['identity'])
62
+ p.quiet! if params['quiet']
63
+ p.stop! if params['stop']
64
+ else
65
+ processes.each do |pro|
66
+ pro.quiet! if params['quiet']
67
+ pro.stop! if params['stop']
68
+ end
69
+ end
70
+
71
+ redirect "#{root_path}busy"
72
+ end
73
+
74
+ get "/queues" do
75
+ @queues = Sidekiq::Queue.all
76
+
77
+ erb(:queues)
78
+ end
79
+
80
+ get "/queues/:name" do
81
+ @name = route_params[:name]
82
+
83
+ halt(404) unless @name
84
+
85
+ @count = (params['count'] || 25).to_i
86
+ @queue = Sidekiq::Queue.new(@name)
87
+ (@current_page, @total_size, @messages) = page("queue:#{@name}", params['page'], @count)
88
+ @messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) }
89
+
90
+ erb(:queue)
91
+ end
92
+
93
+ post "/queues/:name" do
94
+ Sidekiq::Queue.new(route_params[:name]).clear
95
+
96
+ redirect "#{root_path}queues"
97
+ end
98
+
99
+ post "/queues/:name/delete" do
100
+ name = route_params[:name]
101
+ Sidekiq::Job.new(params['key_val'], name).delete
102
+
103
+ redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
104
+ end
105
+
106
+ get '/morgue' do
107
+ @count = (params['count'] || 25).to_i
108
+ (@current_page, @total_size, @dead) = page("dead", params['page'], @count, reverse: true)
109
+ @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
110
+
111
+ erb(:morgue)
112
+ end
113
+
114
+ get "/morgue/:key" do
115
+ halt(404) unless key = route_params[:key]
116
+
117
+ @dead = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
118
+
119
+ if @dead.nil?
120
+ redirect "#{root_path}morgue"
121
+ else
122
+ erb(:dead)
123
+ end
124
+ end
125
+
126
+ get "/errors" do
127
+ @group_by_exception = Sidekiq::DeadSet.new.group_by do |exception|
128
+ exception['error_class']
129
+ end
130
+
131
+ @group_by_class = Sidekiq::DeadSet.new.group_by do |exception|
132
+ exception['wrapped']
133
+ end
134
+
135
+ erb(:errors)
136
+ end
137
+
138
+ post "/errors/retry" do
139
+ jobs_to_retry = Sidekiq::DeadSet.new.each do |hash|
140
+ if (hash['wrapped'] == params['retry_error_class']) || (hash['error_class'] == params['retry_error_exception'])
141
+ hash.retry
142
+ end
143
+ end
144
+
145
+ redirect_with_query("#{root_path}morgue")
146
+ end
147
+
148
+ post "/errors/delete" do
149
+ jobs_to_delete = Sidekiq::DeadSet.new.each do |hash|
150
+ if (hash['wrapped'] == params['delete_error_class']) || (hash['error_class'] == params['delete_error_exception'])
151
+ hash.delete
152
+ end
153
+ end
154
+
155
+ redirect_with_query("#{root_path}morgue")
156
+ end
157
+
158
+ post '/morgue' do
159
+ redirect(request.path) unless params['key']
160
+
161
+ params['key'].each do |key|
162
+ job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
163
+ retry_or_delete_or_kill job, params if job
164
+ end
165
+
166
+ redirect_with_query("#{root_path}morgue")
167
+ end
168
+
169
+ post "/morgue/all/delete" do
170
+ Sidekiq::DeadSet.new.clear
171
+
172
+ redirect "#{root_path}morgue"
173
+ end
174
+
175
+ post "/morgue/all/retry" do
176
+ Sidekiq::DeadSet.new.retry_all
177
+
178
+ redirect "#{root_path}morgue"
179
+ end
180
+
181
+ post "/morgue/:key" do
182
+ halt(404) unless key = route_params[:key]
183
+
184
+ job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
185
+ retry_or_delete_or_kill job, params if job
186
+
187
+ redirect_with_query("#{root_path}morgue")
188
+ end
189
+
190
+ get '/retries' do
191
+ @count = (params['count'] || 25).to_i
192
+ (@current_page, @total_size, @retries) = page("retry", params['page'], @count)
193
+ @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
194
+
195
+ erb(:retries)
196
+ end
197
+
198
+ get "/retries/:key" do
199
+ @retry = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
200
+
201
+ if @retry.nil?
202
+ redirect "#{root_path}retries"
203
+ else
204
+ erb(:retry)
205
+ end
206
+ end
207
+
208
+ post '/retries' do
209
+ redirect(request.path) unless params['key']
210
+
211
+ params['key'].each do |key|
212
+ job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first
213
+ retry_or_delete_or_kill job, params if job
214
+ end
215
+
216
+ redirect_with_query("#{root_path}retries")
217
+ end
218
+
219
+ post "/retries/all/delete" do
220
+ Sidekiq::RetrySet.new.clear
221
+
222
+ redirect "#{root_path}retries"
223
+ end
224
+
225
+ post "/retries/all/retry" do
226
+ Sidekiq::RetrySet.new.retry_all
227
+
228
+ redirect "#{root_path}retries"
229
+ end
230
+
231
+ post "/retries/all/kill" do
232
+ Sidekiq::RetrySet.new.kill_all
233
+
234
+ redirect "#{root_path}retries"
235
+ end
236
+
237
+ post "/retries/:key" do
238
+ job = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
239
+
240
+ retry_or_delete_or_kill job, params if job
241
+
242
+ redirect_with_query("#{root_path}retries")
243
+ end
244
+
245
+ get '/scheduled' do
246
+ @count = (params['count'] || 25).to_i
247
+ (@current_page, @total_size, @scheduled) = page("schedule", params['page'], @count)
248
+ @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
249
+
250
+ erb(:scheduled)
251
+ end
252
+
253
+ get "/scheduled/:key" do
254
+ @job = Sidekiq::ScheduledSet.new.fetch(*parse_params(route_params[:key])).first
255
+
256
+ if @job.nil?
257
+ redirect "#{root_path}scheduled"
258
+ else
259
+ erb(:scheduled_job_info)
260
+ end
261
+ end
262
+
263
+ post '/scheduled' do
264
+ redirect(request.path) unless params['key']
265
+
266
+ params['key'].each do |key|
267
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
268
+ delete_or_add_queue job, params if job
269
+ end
270
+
271
+ redirect_with_query("#{root_path}scheduled")
272
+ end
273
+
274
+ post "/scheduled/:key" do
275
+ halt(404) unless key = route_params[:key]
276
+
277
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
278
+ delete_or_add_queue job, params if job
279
+
280
+ redirect_with_query("#{root_path}scheduled")
281
+ end
282
+
283
+ get '/dashboard/stats' do
284
+ redirect "#{root_path}stats"
285
+ end
286
+
287
+ get '/stats' do
288
+ sidekiq_stats = Sidekiq::Stats.new
289
+ redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
290
+ json(
291
+ sidekiq: {
292
+ processed: sidekiq_stats.processed,
293
+ failed: sidekiq_stats.failed,
294
+ busy: sidekiq_stats.workers_size,
295
+ processes: sidekiq_stats.processes_size,
296
+ enqueued: sidekiq_stats.enqueued,
297
+ scheduled: sidekiq_stats.scheduled_size,
298
+ retries: sidekiq_stats.retry_size,
299
+ dead: sidekiq_stats.dead_size,
300
+ default_latency: sidekiq_stats.default_queue_latency
301
+ },
302
+ redis: redis_stats,
303
+ server_utc_time: server_utc_time
304
+ )
305
+ end
306
+
307
+ get '/stats/queues' do
308
+ json Sidekiq::Stats::Queues.new.lengths
309
+ end
310
+
311
+ def call(env)
312
+ action = self.class.match(env)
313
+ return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass" }, ["Not Found"]] unless action
314
+
315
+ resp = catch(:halt) do
316
+ app = @klass
317
+ self.class.run_befores(app, action)
318
+ begin
319
+ resp = action.instance_exec env, &action.block
320
+ ensure
321
+ self.class.run_afters(app, action)
322
+ end
323
+
324
+ resp
325
+ end
326
+
327
+ resp = case resp
328
+ when Array
329
+ resp
330
+ else
331
+ headers = {
332
+ "Content-Type" => "text/html",
333
+ "Cache-Control" => "no-cache",
334
+ "Content-Language" => action.locale,
335
+ "Content-Security-Policy" => CSP_HEADER
336
+ }
337
+
338
+ [200, headers, [resp]]
339
+ end
340
+
341
+ resp[1] = resp[1].dup
342
+
343
+ resp[1][CONTENT_LENGTH] = resp[2].inject(0) { |l, p| l + p.bytesize }.to_s
344
+
345
+ resp
346
+ end
347
+
348
+ def self.helpers(mod=nil, &block)
349
+ if block_given?
350
+ CleanerAction.class_eval(&block)
351
+ else
352
+ CleanerAction.send(:include, mod)
353
+ end
354
+ end
355
+
356
+ def self.before(path=nil, &block)
357
+ befores << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
358
+ end
359
+
360
+ def self.after(path=nil, &block)
361
+ afters << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
362
+ end
363
+
364
+ def self.run_befores(app, action)
365
+ run_hooks(befores, app, action)
366
+ end
367
+
368
+ def self.run_afters(app, action)
369
+ run_hooks(afters, app, action)
370
+ end
371
+
372
+ def self.run_hooks(hooks, app, action)
373
+ hooks.select { |p,_| !p || p =~ action.env[CleanerRouter::PATH_INFO] }.
374
+ each {|_,b| action.instance_exec(action.env, app, &b) }
375
+ end
376
+
377
+ def self.befores
378
+ @befores ||= []
379
+ end
380
+
381
+ def self.afters
382
+ @afters ||= []
383
+ end
384
+ end
385
+ end