sidekiq 5.0.0

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

Potentially problematic release.


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

Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/.github/contributing.md +32 -0
  3. data/.github/issue_template.md +9 -0
  4. data/.gitignore +13 -0
  5. data/.travis.yml +18 -0
  6. data/3.0-Upgrade.md +70 -0
  7. data/4.0-Upgrade.md +53 -0
  8. data/5.0-Upgrade.md +56 -0
  9. data/COMM-LICENSE +95 -0
  10. data/Changes.md +1402 -0
  11. data/Ent-Changes.md +174 -0
  12. data/Gemfile +29 -0
  13. data/LICENSE +9 -0
  14. data/Pro-2.0-Upgrade.md +138 -0
  15. data/Pro-3.0-Upgrade.md +44 -0
  16. data/Pro-Changes.md +632 -0
  17. data/README.md +107 -0
  18. data/Rakefile +12 -0
  19. data/bin/sidekiq +18 -0
  20. data/bin/sidekiqctl +99 -0
  21. data/bin/sidekiqload +149 -0
  22. data/code_of_conduct.md +50 -0
  23. data/lib/generators/sidekiq/templates/worker.rb.erb +9 -0
  24. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +6 -0
  25. data/lib/generators/sidekiq/templates/worker_test.rb.erb +8 -0
  26. data/lib/generators/sidekiq/worker_generator.rb +49 -0
  27. data/lib/sidekiq.rb +228 -0
  28. data/lib/sidekiq/api.rb +871 -0
  29. data/lib/sidekiq/cli.rb +413 -0
  30. data/lib/sidekiq/client.rb +238 -0
  31. data/lib/sidekiq/core_ext.rb +119 -0
  32. data/lib/sidekiq/delay.rb +21 -0
  33. data/lib/sidekiq/exception_handler.rb +31 -0
  34. data/lib/sidekiq/extensions/action_mailer.rb +57 -0
  35. data/lib/sidekiq/extensions/active_record.rb +40 -0
  36. data/lib/sidekiq/extensions/class_methods.rb +40 -0
  37. data/lib/sidekiq/extensions/generic_proxy.rb +31 -0
  38. data/lib/sidekiq/fetch.rb +81 -0
  39. data/lib/sidekiq/job_logger.rb +27 -0
  40. data/lib/sidekiq/job_retry.rb +235 -0
  41. data/lib/sidekiq/launcher.rb +167 -0
  42. data/lib/sidekiq/logging.rb +106 -0
  43. data/lib/sidekiq/manager.rb +138 -0
  44. data/lib/sidekiq/middleware/chain.rb +150 -0
  45. data/lib/sidekiq/middleware/i18n.rb +42 -0
  46. data/lib/sidekiq/middleware/server/active_record.rb +22 -0
  47. data/lib/sidekiq/paginator.rb +43 -0
  48. data/lib/sidekiq/processor.rb +238 -0
  49. data/lib/sidekiq/rails.rb +60 -0
  50. data/lib/sidekiq/redis_connection.rb +106 -0
  51. data/lib/sidekiq/scheduled.rb +147 -0
  52. data/lib/sidekiq/testing.rb +324 -0
  53. data/lib/sidekiq/testing/inline.rb +29 -0
  54. data/lib/sidekiq/util.rb +63 -0
  55. data/lib/sidekiq/version.rb +4 -0
  56. data/lib/sidekiq/web.rb +213 -0
  57. data/lib/sidekiq/web/action.rb +89 -0
  58. data/lib/sidekiq/web/application.rb +331 -0
  59. data/lib/sidekiq/web/helpers.rb +286 -0
  60. data/lib/sidekiq/web/router.rb +100 -0
  61. data/lib/sidekiq/worker.rb +144 -0
  62. data/sidekiq.gemspec +32 -0
  63. data/web/assets/images/favicon.ico +0 -0
  64. data/web/assets/images/logo.png +0 -0
  65. data/web/assets/images/status.png +0 -0
  66. data/web/assets/javascripts/application.js +92 -0
  67. data/web/assets/javascripts/dashboard.js +298 -0
  68. data/web/assets/stylesheets/application-rtl.css +246 -0
  69. data/web/assets/stylesheets/application.css +1111 -0
  70. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  71. data/web/assets/stylesheets/bootstrap.css +5 -0
  72. data/web/locales/ar.yml +80 -0
  73. data/web/locales/cs.yml +78 -0
  74. data/web/locales/da.yml +68 -0
  75. data/web/locales/de.yml +69 -0
  76. data/web/locales/el.yml +68 -0
  77. data/web/locales/en.yml +79 -0
  78. data/web/locales/es.yml +69 -0
  79. data/web/locales/fa.yml +80 -0
  80. data/web/locales/fr.yml +78 -0
  81. data/web/locales/he.yml +79 -0
  82. data/web/locales/hi.yml +75 -0
  83. data/web/locales/it.yml +69 -0
  84. data/web/locales/ja.yml +78 -0
  85. data/web/locales/ko.yml +68 -0
  86. data/web/locales/nb.yml +77 -0
  87. data/web/locales/nl.yml +68 -0
  88. data/web/locales/pl.yml +59 -0
  89. data/web/locales/pt-br.yml +68 -0
  90. data/web/locales/pt.yml +67 -0
  91. data/web/locales/ru.yml +78 -0
  92. data/web/locales/sv.yml +68 -0
  93. data/web/locales/ta.yml +75 -0
  94. data/web/locales/uk.yml +76 -0
  95. data/web/locales/ur.yml +80 -0
  96. data/web/locales/zh-cn.yml +68 -0
  97. data/web/locales/zh-tw.yml +68 -0
  98. data/web/views/_footer.erb +17 -0
  99. data/web/views/_job_info.erb +88 -0
  100. data/web/views/_nav.erb +66 -0
  101. data/web/views/_paging.erb +23 -0
  102. data/web/views/_poll_link.erb +7 -0
  103. data/web/views/_status.erb +4 -0
  104. data/web/views/_summary.erb +40 -0
  105. data/web/views/busy.erb +94 -0
  106. data/web/views/dashboard.erb +75 -0
  107. data/web/views/dead.erb +34 -0
  108. data/web/views/layout.erb +40 -0
  109. data/web/views/morgue.erb +75 -0
  110. data/web/views/queue.erb +45 -0
  111. data/web/views/queues.erb +28 -0
  112. data/web/views/retries.erb +76 -0
  113. data/web/views/retry.erb +34 -0
  114. data/web/views/scheduled.erb +54 -0
  115. data/web/views/scheduled_job_info.erb +8 -0
  116. metadata +366 -0
@@ -0,0 +1,286 @@
1
+ # frozen_string_literal: true
2
+ require 'uri'
3
+ require 'set'
4
+ require 'yaml'
5
+ require 'cgi'
6
+
7
+ module Sidekiq
8
+ # This is not a public API
9
+ module WebHelpers
10
+ def strings(lang)
11
+ @@strings ||= {}
12
+ @@strings[lang] ||= begin
13
+ # Allow sidekiq-web extensions to add locale paths
14
+ # so extensions can be localized
15
+ settings.locales.each_with_object({}) do |path, global|
16
+ find_locale_files(lang).each do |file|
17
+ strs = YAML.load(File.open(file))
18
+ global.deep_merge!(strs[lang])
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ def clear_caches
25
+ @@strings = nil
26
+ @@locale_files = nil
27
+ end
28
+
29
+ def locale_files
30
+ @@locale_files ||= settings.locales.flat_map do |path|
31
+ Dir["#{path}/*.yml"]
32
+ end
33
+ end
34
+
35
+ def find_locale_files(lang)
36
+ locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
37
+ end
38
+
39
+ # This is a hook for a Sidekiq Pro feature. Please don't touch.
40
+ def filtering(*)
41
+ end
42
+
43
+ # This view helper provide ability display you html code in
44
+ # to head of page. Example:
45
+ #
46
+ # <% add_to_head do %>
47
+ # <link rel="stylesheet" .../>
48
+ # <meta .../>
49
+ # <% end %>
50
+ #
51
+ def add_to_head
52
+ @head_html ||= []
53
+ @head_html << yield.dup if block_given?
54
+ end
55
+
56
+ def display_custom_head
57
+ @head_html.join if defined?(@head_html)
58
+ end
59
+
60
+ def poll_path
61
+ if current_path != '' && params['poll']
62
+ root_path + current_path
63
+ else
64
+ ""
65
+ end
66
+ end
67
+
68
+ def text_direction
69
+ get_locale['TextDirection'] || 'ltr'
70
+ end
71
+
72
+ def rtl?
73
+ text_direction == 'rtl'
74
+ end
75
+
76
+ # Given a browser request Accept-Language header like
77
+ # "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2", this function
78
+ # will return "fr" since that's the first code with a matching
79
+ # locale in web/locales
80
+ def locale
81
+ @locale ||= begin
82
+ locale = 'en'.freeze
83
+ languages = env['HTTP_ACCEPT_LANGUAGE'.freeze] || 'en'.freeze
84
+ languages.downcase.split(','.freeze).each do |lang|
85
+ next if lang == '*'.freeze
86
+ lang = lang.split(';'.freeze)[0]
87
+ break locale = lang if find_locale_files(lang).any?
88
+ end
89
+ locale
90
+ end
91
+ end
92
+
93
+ # mperham/sidekiq#3243
94
+ def unfiltered?
95
+ yield unless env['PATH_INFO'].start_with?("/filter/")
96
+ end
97
+
98
+ def get_locale
99
+ strings(locale)
100
+ end
101
+
102
+ def t(msg, options={})
103
+ string = get_locale[msg] || msg
104
+ if options.empty?
105
+ string
106
+ else
107
+ string % options
108
+ end
109
+ end
110
+
111
+ def workers
112
+ @workers ||= Sidekiq::Workers.new
113
+ end
114
+
115
+ def processes
116
+ @processes ||= Sidekiq::ProcessSet.new
117
+ end
118
+
119
+ def stats
120
+ @stats ||= Sidekiq::Stats.new
121
+ end
122
+
123
+ def retries_with_score(score)
124
+ Sidekiq.redis do |conn|
125
+ conn.zrangebyscore('retry', score, score)
126
+ end.map { |msg| Sidekiq.load_json(msg) }
127
+ end
128
+
129
+ def redis_connection
130
+ Sidekiq.redis { |conn| conn.client.id }
131
+ end
132
+
133
+ def namespace
134
+ @@ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
135
+ end
136
+
137
+ def redis_info
138
+ Sidekiq.redis_info
139
+ end
140
+
141
+ def root_path
142
+ "#{env['SCRIPT_NAME']}/"
143
+ end
144
+
145
+ def current_path
146
+ @current_path ||= request.path_info.gsub(/^\//,'')
147
+ end
148
+
149
+ def current_status
150
+ workers.size == 0 ? 'idle' : 'active'
151
+ end
152
+
153
+ def relative_time(time)
154
+ stamp = time.getutc.iso8601
155
+ %{<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>}
156
+ end
157
+
158
+ def job_params(job, score)
159
+ "#{score}-#{job['jid']}"
160
+ end
161
+
162
+ def parse_params(params)
163
+ score, jid = params.split("-")
164
+ [score.to_f, jid]
165
+ end
166
+
167
+ SAFE_QPARAMS = %w(page poll)
168
+
169
+ # Merge options with current params, filter safe params, and stringify to query string
170
+ def qparams(options)
171
+ options = options.stringify_keys
172
+ params.merge(options).map do |key, value|
173
+ SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
174
+ end.compact.join("&")
175
+ end
176
+
177
+ def truncate(text, truncate_after_chars = 2000)
178
+ truncate_after_chars && text.size > truncate_after_chars ? "#{text[0..truncate_after_chars]}..." : text
179
+ end
180
+
181
+ def display_args(args, truncate_after_chars = 2000)
182
+ args.map do |arg|
183
+ h(truncate(to_display(arg), truncate_after_chars))
184
+ end.join(", ")
185
+ end
186
+
187
+ def csrf_tag
188
+ "<input type='hidden' name='authenticity_token' value='#{session[:csrf]}'/>"
189
+ end
190
+
191
+ def to_display(arg)
192
+ begin
193
+ arg.inspect
194
+ rescue
195
+ begin
196
+ arg.to_s
197
+ rescue => ex
198
+ "Cannot display argument: [#{ex.class.name}] #{ex.message}"
199
+ end
200
+ end
201
+ end
202
+
203
+ RETRY_JOB_KEYS = Set.new(%w(
204
+ queue class args retry_count retried_at failed_at
205
+ jid error_message error_class backtrace
206
+ error_backtrace enqueued_at retry wrapped
207
+ created_at
208
+ ))
209
+
210
+ def retry_extra_items(retry_job)
211
+ @retry_extra_items ||= {}.tap do |extra|
212
+ retry_job.item.each do |key, value|
213
+ extra[key] = value unless RETRY_JOB_KEYS.include?(key)
214
+ end
215
+ end
216
+ end
217
+
218
+ def number_with_delimiter(number)
219
+ begin
220
+ Float(number)
221
+ rescue ArgumentError, TypeError
222
+ return number
223
+ end
224
+
225
+ options = {delimiter: ',', separator: '.'}
226
+ parts = number.to_s.to_str.split('.')
227
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
228
+ parts.join(options[:separator])
229
+ end
230
+
231
+ def h(text)
232
+ ::Rack::Utils.escape_html(text)
233
+ rescue ArgumentError => e
234
+ raise unless e.message.eql?('invalid byte sequence in UTF-8')
235
+ text.encode!('UTF-16', 'UTF-8', invalid: :replace, replace: '').encode!('UTF-8', 'UTF-16')
236
+ retry
237
+ end
238
+
239
+ # Any paginated list that performs an action needs to redirect
240
+ # back to the proper page after performing that action.
241
+ def redirect_with_query(url)
242
+ r = request.referer
243
+ if r && r =~ /\?/
244
+ ref = URI(r)
245
+ redirect("#{url}?#{ref.query}")
246
+ else
247
+ redirect url
248
+ end
249
+ end
250
+
251
+ def environment_title_prefix
252
+ environment = Sidekiq.options[:environment] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
253
+
254
+ "[#{environment.upcase}] " unless environment == "production"
255
+ end
256
+
257
+ def product_version
258
+ "Sidekiq v#{Sidekiq::VERSION}"
259
+ end
260
+
261
+ def redis_connection_and_namespace
262
+ @redis_connection_and_namespace ||= begin
263
+ namespace_suffix = namespace == nil ? '' : "##{namespace}"
264
+ "#{redis_connection}#{namespace_suffix}"
265
+ end
266
+ end
267
+
268
+ def retry_or_delete_or_kill(job, params)
269
+ if params['retry']
270
+ job.retry
271
+ elsif params['delete']
272
+ job.delete
273
+ elsif params['kill']
274
+ job.kill
275
+ end
276
+ end
277
+
278
+ def delete_or_add_queue(job, params)
279
+ if params['delete']
280
+ job.delete
281
+ elsif params['add_to_queue']
282
+ job.add_to_queue
283
+ end
284
+ end
285
+ end
286
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+ require 'rack'
3
+
4
+ module Sidekiq
5
+ module WebRouter
6
+ GET = 'GET'.freeze
7
+ DELETE = 'DELETE'.freeze
8
+ POST = 'POST'.freeze
9
+ PUT = 'PUT'.freeze
10
+ PATCH = 'PATCH'.freeze
11
+ HEAD = 'HEAD'.freeze
12
+
13
+ ROUTE_PARAMS = 'rack.route_params'.freeze
14
+ REQUEST_METHOD = 'REQUEST_METHOD'.freeze
15
+ PATH_INFO = 'PATH_INFO'.freeze
16
+
17
+ def get(path, &block)
18
+ route(GET, path, &block)
19
+ end
20
+
21
+ def post(path, &block)
22
+ route(POST, path, &block)
23
+ end
24
+
25
+ def put(path, &block)
26
+ route(PUT, path, &block)
27
+ end
28
+
29
+ def patch(path, &block)
30
+ route(PATCH, path, &block)
31
+ end
32
+
33
+ def delete(path, &block)
34
+ route(DELETE, path, &block)
35
+ end
36
+
37
+ def route(method, path, &block)
38
+ @routes ||= { GET => [], POST => [], PUT => [], PATCH => [], DELETE => [], HEAD => [] }
39
+
40
+ @routes[method] << WebRoute.new(method, path, block)
41
+ @routes[HEAD] << WebRoute.new(method, path, block) if method == GET
42
+ end
43
+
44
+ def match(env)
45
+ request_method = env[REQUEST_METHOD]
46
+ path_info = ::Rack::Utils.unescape env[PATH_INFO]
47
+
48
+ # There are servers which send an empty string when requesting the root.
49
+ # These servers should be ashamed of themselves.
50
+ path_info = "/" if path_info == ""
51
+
52
+ @routes[request_method].each do |route|
53
+ if params = route.match(request_method, path_info)
54
+ env[ROUTE_PARAMS] = params
55
+
56
+ return WebAction.new(env, route.block)
57
+ end
58
+ end
59
+
60
+ nil
61
+ end
62
+ end
63
+
64
+ class WebRoute
65
+ attr_accessor :request_method, :pattern, :block, :name
66
+
67
+ NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^\.:$\/]+)/.freeze
68
+
69
+ def initialize(request_method, pattern, block)
70
+ @request_method = request_method
71
+ @pattern = pattern
72
+ @block = block
73
+ end
74
+
75
+ def matcher
76
+ @matcher ||= compile
77
+ end
78
+
79
+ def compile
80
+ if pattern.match(NAMED_SEGMENTS_PATTERN)
81
+ p = pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^$/]+)')
82
+
83
+ Regexp.new("\\A#{p}\\Z")
84
+ else
85
+ pattern
86
+ end
87
+ end
88
+
89
+ def match(request_method, path)
90
+ case matcher
91
+ when String
92
+ {} if path == matcher
93
+ else
94
+ if path_match = path.match(matcher)
95
+ Hash[path_match.names.map(&:to_sym).zip(path_match.captures)]
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+ require 'sidekiq/client'
3
+ require 'sidekiq/core_ext'
4
+
5
+ module Sidekiq
6
+
7
+
8
+ ##
9
+ # Include this module in your worker class and you can easily create
10
+ # asynchronous jobs:
11
+ #
12
+ # class HardWorker
13
+ # include Sidekiq::Worker
14
+ #
15
+ # def perform(*args)
16
+ # # do some work
17
+ # end
18
+ # end
19
+ #
20
+ # Then in your Rails app, you can do this:
21
+ #
22
+ # HardWorker.perform_async(1, 2, 3)
23
+ #
24
+ # Note that perform_async is a class method, perform is an instance method.
25
+ module Worker
26
+ attr_accessor :jid
27
+
28
+ def self.included(base)
29
+ raise ArgumentError, "You cannot include Sidekiq::Worker in an ActiveJob: #{base.name}" if base.ancestors.any? {|c| c.name == 'ActiveJob::Base' }
30
+
31
+ base.extend(ClassMethods)
32
+ base.class_attribute :sidekiq_options_hash
33
+ base.class_attribute :sidekiq_retry_in_block
34
+ base.class_attribute :sidekiq_retries_exhausted_block
35
+ end
36
+
37
+ def logger
38
+ Sidekiq.logger
39
+ end
40
+
41
+ # This helper class encapsulates the set options for `set`, e.g.
42
+ #
43
+ # SomeWorker.set(queue: 'foo').perform_async(....)
44
+ #
45
+ class Setter
46
+ def initialize(opts)
47
+ @opts = opts
48
+ end
49
+
50
+ def perform_async(*args)
51
+ @opts['class'.freeze].client_push(@opts.merge!('args'.freeze => args))
52
+ end
53
+
54
+ # +interval+ must be a timestamp, numeric or something that acts
55
+ # numeric (like an activesupport time interval).
56
+ def perform_in(interval, *args)
57
+ int = interval.to_f
58
+ now = Time.now.to_f
59
+ ts = (int < 1_000_000_000 ? now + int : int)
60
+
61
+ @opts.merge! 'args'.freeze => args, 'at'.freeze => ts
62
+ # Optimization to enqueue something now that is scheduled to go out now or in the past
63
+ @opts.delete('at'.freeze) if ts <= now
64
+ @opts['class'.freeze].client_push(@opts)
65
+ end
66
+ alias_method :perform_at, :perform_in
67
+ end
68
+
69
+ module ClassMethods
70
+
71
+ def delay(*args)
72
+ raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async"
73
+ end
74
+
75
+ def delay_for(*args)
76
+ raise ArgumentError, "Do not call .delay_for on a Sidekiq::Worker class, call .perform_in"
77
+ end
78
+
79
+ def delay_until(*args)
80
+ raise ArgumentError, "Do not call .delay_until on a Sidekiq::Worker class, call .perform_at"
81
+ end
82
+
83
+ def set(options)
84
+ Setter.new(options.merge!('class'.freeze => self))
85
+ end
86
+
87
+ def perform_async(*args)
88
+ client_push('class'.freeze => self, 'args'.freeze => args)
89
+ end
90
+
91
+ # +interval+ must be a timestamp, numeric or something that acts
92
+ # numeric (like an activesupport time interval).
93
+ def perform_in(interval, *args)
94
+ int = interval.to_f
95
+ now = Time.now.to_f
96
+ ts = (int < 1_000_000_000 ? now + int : int)
97
+
98
+ item = { 'class'.freeze => self, 'args'.freeze => args, 'at'.freeze => ts }
99
+
100
+ # Optimization to enqueue something now that is scheduled to go out now or in the past
101
+ item.delete('at'.freeze) if ts <= now
102
+
103
+ client_push(item)
104
+ end
105
+ alias_method :perform_at, :perform_in
106
+
107
+ ##
108
+ # Allows customization for this type of Worker.
109
+ # Legal options:
110
+ #
111
+ # queue - use a named queue for this Worker, default 'default'
112
+ # retry - enable the RetryJobs middleware for this Worker, *true* to use the default
113
+ # or *Integer* count
114
+ # backtrace - whether to save any error backtrace in the retry payload to display in web UI,
115
+ # can be true, false or an integer number of lines to save, default *false*
116
+ # pool - use the given Redis connection pool to push this type of job to a given shard.
117
+ #
118
+ # In practice, any option is allowed. This is the main mechanism to configure the
119
+ # options for a specific job.
120
+ def sidekiq_options(opts={})
121
+ self.sidekiq_options_hash = get_sidekiq_options.merge(opts.stringify_keys)
122
+ end
123
+
124
+ def sidekiq_retry_in(&block)
125
+ self.sidekiq_retry_in_block = block
126
+ end
127
+
128
+ def sidekiq_retries_exhausted(&block)
129
+ self.sidekiq_retries_exhausted_block = block
130
+ end
131
+
132
+ def get_sidekiq_options # :nodoc:
133
+ self.sidekiq_options_hash ||= Sidekiq.default_worker_options
134
+ end
135
+
136
+ def client_push(item) # :nodoc:
137
+ pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options['pool'.freeze] || Sidekiq.redis_pool
138
+ hash = item.stringify_keys
139
+ Sidekiq::Client.new(pool).push(hash)
140
+ end
141
+
142
+ end
143
+ end
144
+ end