sidekiq_cleaner 5.3.6

Sign up to get free protection for your applications and to get access to all the features.
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