sidekiq 5.2.8 → 6.1.3

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
  3. data/.github/workflows/ci.yml +41 -0
  4. data/.gitignore +0 -2
  5. data/.standard.yml +20 -0
  6. data/5.0-Upgrade.md +1 -1
  7. data/6.0-Upgrade.md +72 -0
  8. data/Changes.md +196 -0
  9. data/Ent-2.0-Upgrade.md +37 -0
  10. data/Ent-Changes.md +72 -1
  11. data/Gemfile +12 -11
  12. data/Gemfile.lock +193 -0
  13. data/Pro-5.0-Upgrade.md +25 -0
  14. data/Pro-Changes.md +56 -2
  15. data/README.md +18 -34
  16. data/Rakefile +5 -4
  17. data/bin/sidekiq +26 -2
  18. data/bin/sidekiqload +32 -24
  19. data/bin/sidekiqmon +8 -0
  20. data/lib/generators/sidekiq/templates/worker_test.rb.erb +1 -1
  21. data/lib/generators/sidekiq/worker_generator.rb +21 -13
  22. data/lib/sidekiq/api.rb +245 -219
  23. data/lib/sidekiq/cli.rb +144 -180
  24. data/lib/sidekiq/client.rb +68 -48
  25. data/lib/sidekiq/delay.rb +5 -6
  26. data/lib/sidekiq/exception_handler.rb +10 -12
  27. data/lib/sidekiq/extensions/action_mailer.rb +13 -22
  28. data/lib/sidekiq/extensions/active_record.rb +13 -10
  29. data/lib/sidekiq/extensions/class_methods.rb +14 -11
  30. data/lib/sidekiq/extensions/generic_proxy.rb +4 -4
  31. data/lib/sidekiq/fetch.rb +29 -30
  32. data/lib/sidekiq/job_logger.rb +45 -7
  33. data/lib/sidekiq/job_retry.rb +62 -61
  34. data/lib/sidekiq/launcher.rb +112 -54
  35. data/lib/sidekiq/logger.rb +166 -0
  36. data/lib/sidekiq/manager.rb +11 -13
  37. data/lib/sidekiq/middleware/chain.rb +15 -5
  38. data/lib/sidekiq/middleware/i18n.rb +5 -7
  39. data/lib/sidekiq/monitor.rb +133 -0
  40. data/lib/sidekiq/paginator.rb +18 -14
  41. data/lib/sidekiq/processor.rb +71 -70
  42. data/lib/sidekiq/rails.rb +29 -37
  43. data/lib/sidekiq/redis_connection.rb +50 -48
  44. data/lib/sidekiq/scheduled.rb +28 -29
  45. data/lib/sidekiq/sd_notify.rb +149 -0
  46. data/lib/sidekiq/systemd.rb +24 -0
  47. data/lib/sidekiq/testing/inline.rb +2 -1
  48. data/lib/sidekiq/testing.rb +35 -24
  49. data/lib/sidekiq/util.rb +17 -16
  50. data/lib/sidekiq/version.rb +2 -1
  51. data/lib/sidekiq/web/action.rb +14 -10
  52. data/lib/sidekiq/web/application.rb +74 -72
  53. data/lib/sidekiq/web/csrf_protection.rb +156 -0
  54. data/lib/sidekiq/web/helpers.rb +97 -77
  55. data/lib/sidekiq/web/router.rb +18 -17
  56. data/lib/sidekiq/web.rb +53 -53
  57. data/lib/sidekiq/worker.rb +126 -102
  58. data/lib/sidekiq.rb +69 -44
  59. data/sidekiq.gemspec +15 -16
  60. data/web/assets/javascripts/application.js +25 -27
  61. data/web/assets/javascripts/dashboard.js +4 -23
  62. data/web/assets/stylesheets/application-dark.css +149 -0
  63. data/web/assets/stylesheets/application.css +28 -6
  64. data/web/locales/de.yml +14 -2
  65. data/web/locales/en.yml +2 -0
  66. data/web/locales/fr.yml +3 -3
  67. data/web/locales/ja.yml +4 -1
  68. data/web/locales/lt.yml +83 -0
  69. data/web/locales/pl.yml +4 -4
  70. data/web/locales/ru.yml +4 -0
  71. data/web/locales/vi.yml +83 -0
  72. data/web/views/_job_info.erb +2 -1
  73. data/web/views/busy.erb +8 -3
  74. data/web/views/dead.erb +2 -2
  75. data/web/views/layout.erb +1 -0
  76. data/web/views/morgue.erb +5 -2
  77. data/web/views/queue.erb +10 -1
  78. data/web/views/queues.erb +9 -1
  79. data/web/views/retries.erb +5 -2
  80. data/web/views/retry.erb +2 -2
  81. data/web/views/scheduled.erb +5 -2
  82. metadata +31 -49
  83. data/.circleci/config.yml +0 -61
  84. data/.github/issue_template.md +0 -11
  85. data/.travis.yml +0 -11
  86. data/bin/sidekiqctl +0 -20
  87. data/lib/sidekiq/core_ext.rb +0 -1
  88. data/lib/sidekiq/ctl.rb +0 -221
  89. data/lib/sidekiq/logging.rb +0 -122
  90. data/lib/sidekiq/middleware/server/active_record.rb +0 -23
data/lib/sidekiq/rails.rb CHANGED
@@ -1,38 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "sidekiq/worker"
4
+
3
5
  module Sidekiq
4
6
  class Rails < ::Rails::Engine
5
- # We need to setup this up before any application configuration which might
6
- # change Sidekiq middleware.
7
- #
8
- # This hook happens after `Rails::Application` is inherited within
9
- # config/application.rb and before config is touched, usually within the
10
- # class block. Definitely before config/environments/*.rb and
11
- # config/initializers/*.rb.
12
- config.before_configuration do
13
- if ::Rails::VERSION::MAJOR < 5 && defined?(::ActiveRecord)
14
- Sidekiq.server_middleware do |chain|
15
- require 'sidekiq/middleware/server/active_record'
16
- chain.add Sidekiq::Middleware::Server::ActiveRecord
17
- end
18
- end
19
- end
20
-
21
- config.after_initialize do
22
- # This hook happens after all initializers are run, just before returning
23
- # from config/environment.rb back to sidekiq/cli.rb.
24
- # We have to add the reloader after initialize to see if cache_classes has
25
- # been turned on.
26
- #
27
- # None of this matters on the client-side, only within the Sidekiq process itself.
28
- #
29
- Sidekiq.configure_server do |_|
30
- if ::Rails::VERSION::MAJOR >= 5
31
- Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
32
- end
33
- end
34
- end
35
-
36
7
  class Reloader
37
8
  def initialize(app = ::Rails.application)
38
9
  @app = app
@@ -48,11 +19,32 @@ module Sidekiq
48
19
  "#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
49
20
  end
50
21
  end
51
- end if defined?(::Rails)
52
- end
53
22
 
54
- if defined?(::Rails) && ::Rails::VERSION::MAJOR < 4
55
- $stderr.puts("**************************************************")
56
- $stderr.puts("⛔️ WARNING: Sidekiq server is no longer supported by Rails 3.2 - please ensure your server/workers are updated")
57
- $stderr.puts("**************************************************")
23
+ # By including the Options module, we allow AJs to directly control sidekiq features
24
+ # via the *sidekiq_options* class method and, for instance, not use AJ's retry system.
25
+ # AJ retries don't show up in the Sidekiq UI Retries tab, save any error data, can't be
26
+ # manually retried, don't automatically die, etc.
27
+ #
28
+ # class SomeJob < ActiveJob::Base
29
+ # queue_as :default
30
+ # sidekiq_options retry: 3, backtrace: 10
31
+ # def perform
32
+ # end
33
+ # end
34
+ initializer "sidekiq.active_job_integration" do
35
+ ActiveSupport.on_load(:active_job) do
36
+ include ::Sidekiq::Worker::Options unless respond_to?(:sidekiq_options)
37
+ end
38
+ end
39
+
40
+ # This hook happens after all initializers are run, just before returning
41
+ # from config/environment.rb back to sidekiq/cli.rb.
42
+ #
43
+ # None of this matters on the client-side, only within the Sidekiq process itself.
44
+ config.after_initialize do
45
+ Sidekiq.configure_server do |_|
46
+ Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
47
+ end
48
+ end
49
+ end
58
50
  end
@@ -1,37 +1,38 @@
1
1
  # frozen_string_literal: true
2
- require 'connection_pool'
3
- require 'redis'
4
- require 'uri'
2
+
3
+ require "connection_pool"
4
+ require "redis"
5
+ require "uri"
5
6
 
6
7
  module Sidekiq
7
8
  class RedisConnection
8
9
  class << self
10
+ def create(options = {})
11
+ symbolized_options = options.transform_keys(&:to_sym)
9
12
 
10
- def create(options={})
11
- options.keys.each do |key|
12
- options[key.to_sym] = options.delete(key)
13
+ if !symbolized_options[:url] && (u = determine_redis_provider)
14
+ symbolized_options[:url] = u
13
15
  end
14
16
 
15
- options[:id] = "Sidekiq-#{Sidekiq.server? ? "server" : "client"}-PID-#{$$}" if !options.has_key?(:id)
16
- options[:url] ||= determine_redis_provider
17
-
18
- size = if options[:size]
19
- options[:size]
20
- elsif Sidekiq.server?
21
- Sidekiq.options[:concurrency] + 5
22
- elsif ENV['RAILS_MAX_THREADS']
23
- Integer(ENV['RAILS_MAX_THREADS'])
24
- else
25
- 5
26
- end
17
+ size = if symbolized_options[:size]
18
+ symbolized_options[:size]
19
+ elsif Sidekiq.server?
20
+ # Give ourselves plenty of connections. pool is lazy
21
+ # so we won't create them until we need them.
22
+ Sidekiq.options[:concurrency] + 5
23
+ elsif ENV["RAILS_MAX_THREADS"]
24
+ Integer(ENV["RAILS_MAX_THREADS"])
25
+ else
26
+ 5
27
+ end
27
28
 
28
29
  verify_sizing(size, Sidekiq.options[:concurrency]) if Sidekiq.server?
29
30
 
30
- pool_timeout = options[:pool_timeout] || 1
31
- log_info(options)
31
+ pool_timeout = symbolized_options[:pool_timeout] || 1
32
+ log_info(symbolized_options)
32
33
 
33
- ConnectionPool.new(:timeout => pool_timeout, :size => size) do
34
- build_client(options)
34
+ ConnectionPool.new(timeout: pool_timeout, size: size) do
35
+ build_client(symbolized_options)
35
36
  end
36
37
  end
37
38
 
@@ -46,7 +47,7 @@ module Sidekiq
46
47
  # - enterprise's leader election
47
48
  # - enterprise's cron support
48
49
  def verify_sizing(size, concurrency)
49
- raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work. Your pool has #{size} connections but must have at least #{concurrency + 2}" if size <= concurrency
50
+ raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work. Your pool has #{size} connections but must have at least #{concurrency + 2}" if size < (concurrency + 2)
50
51
  end
51
52
 
52
53
  def build_client(options)
@@ -55,8 +56,8 @@ module Sidekiq
55
56
  client = Redis.new client_opts(options)
56
57
  if namespace
57
58
  begin
58
- require 'redis/namespace'
59
- Redis::Namespace.new(namespace, :redis => client)
59
+ require "redis/namespace"
60
+ Redis::Namespace.new(namespace, redis: client)
60
61
  rescue LoadError
61
62
  Sidekiq.logger.error("Your Redis configuration uses the namespace '#{namespace}' but the redis-namespace gem is not included in the Gemfile." \
62
63
  "Add the gem to your Gemfile to continue using a namespace. Otherwise, remove the namespace parameter.")
@@ -78,7 +79,7 @@ module Sidekiq
78
79
  opts.delete(:network_timeout)
79
80
  end
80
81
 
81
- opts[:driver] ||= Redis::Connection.drivers.last || 'ruby'
82
+ opts[:driver] ||= Redis::Connection.drivers.last || "ruby"
82
83
 
83
84
  # Issue #3303, redis-rb will silently retry an operation.
84
85
  # This can lead to duplicate jobs if Sidekiq::Client's LPUSH
@@ -91,9 +92,15 @@ module Sidekiq
91
92
  end
92
93
 
93
94
  def log_info(options)
94
- # Don't log Redis AUTH password
95
95
  redacted = "REDACTED"
96
- scrubbed_options = options.dup
96
+
97
+ # deep clone so we can muck with these options all we want
98
+ #
99
+ # exclude SSL params from dump-and-load because some information isn't
100
+ # safely dumpable in current Rubies
101
+ keys = options.keys
102
+ keys.delete(:ssl_params)
103
+ scrubbed_options = Marshal.load(Marshal.dump(options.slice(*keys)))
97
104
  if scrubbed_options[:url] && (uri = URI.parse(scrubbed_options[:url])) && uri.password
98
105
  uri.password = redacted
99
106
  scrubbed_options[:url] = uri.to_s
@@ -101,6 +108,9 @@ module Sidekiq
101
108
  if scrubbed_options[:password]
102
109
  scrubbed_options[:password] = redacted
103
110
  end
111
+ scrubbed_options[:sentinels]&.each do |sentinel|
112
+ sentinel[:password] = redacted if sentinel[:password]
113
+ end
104
114
  if Sidekiq.server?
105
115
  Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} with redis options #{scrubbed_options}")
106
116
  else
@@ -115,30 +125,22 @@ module Sidekiq
115
125
  # REDIS_PROVIDER=MY_REDIS_URL
116
126
  # and Sidekiq will find your custom URL variable with no custom
117
127
  # initialization code at all.
118
- p = ENV['REDIS_PROVIDER']
119
- if p && p =~ /\:/
120
- Sidekiq.logger.error <<-EOM
121
-
122
- #################################################################################
123
-
124
- REDIS_PROVIDER should be set to the **name** of the variable which contains the Redis URL, not a URL itself.
125
- Platforms like Heroku sell addons that publish a *_URL variable. You tell Sidekiq with REDIS_PROVIDER, e.g.:
126
-
127
- REDIS_PROVIDER=REDISTOGO_URL
128
- REDISTOGO_URL=redis://somehost.example.com:6379/4
129
-
130
- Use REDIS_URL if you wish to point Sidekiq to a URL directly.
131
-
132
- This configuration error will crash starting in Sidekiq 5.3.
133
-
134
- #################################################################################
135
- EOM
128
+ #
129
+ p = ENV["REDIS_PROVIDER"]
130
+ if p && p =~ /:/
131
+ raise <<~EOM
132
+ REDIS_PROVIDER should be set to the name of the variable which contains the Redis URL, not a URL itself.
133
+ Platforms like Heroku will sell addons that publish a *_URL variable. You need to tell Sidekiq with REDIS_PROVIDER, e.g.:
134
+
135
+ REDISTOGO_URL=redis://somehost.example.com:6379/4
136
+ REDIS_PROVIDER=REDISTOGO_URL
137
+ EOM
136
138
  end
139
+
137
140
  ENV[
138
- ENV['REDIS_PROVIDER'] || 'REDIS_URL'
141
+ p || "REDIS_URL"
139
142
  ]
140
143
  end
141
-
142
144
  end
143
145
  end
144
146
  end
@@ -1,30 +1,32 @@
1
1
  # frozen_string_literal: true
2
- require 'sidekiq'
3
- require 'sidekiq/util'
4
- require 'sidekiq/api'
2
+
3
+ require "sidekiq"
4
+ require "sidekiq/util"
5
+ require "sidekiq/api"
5
6
 
6
7
  module Sidekiq
7
8
  module Scheduled
8
- SETS = %w(retry schedule)
9
+ SETS = %w[retry schedule]
9
10
 
10
11
  class Enq
11
- def enqueue_jobs(now=Time.now.to_f.to_s, sorted_sets=SETS)
12
+ def enqueue_jobs(now = Time.now.to_f.to_s, sorted_sets = SETS)
12
13
  # A job's "score" in Redis is the time at which it should be processed.
13
14
  # Just check Redis for the set of jobs with a timestamp before now.
14
15
  Sidekiq.redis do |conn|
15
16
  sorted_sets.each do |sorted_set|
16
- # Get the next item in the queue if it's score (time to execute) is <= now.
17
- # We need to go through the list one at a time to reduce the risk of something
18
- # going wrong between the time jobs are popped from the scheduled queue and when
19
- # they are pushed onto a work queue and losing the jobs.
20
- while job = conn.zrangebyscore(sorted_set, '-inf', now, :limit => [0, 1]).first do
21
-
22
- # Pop item off the queue and add it to the work queue. If the job can't be popped from
23
- # the queue, it's because another process already popped it so we can move on to the
24
- # next one.
25
- if conn.zrem(sorted_set, job)
26
- Sidekiq::Client.push(Sidekiq.load_json(job))
27
- Sidekiq::Logging.logger.debug { "enqueued #{sorted_set}: #{job}" }
17
+ # Get next items in the queue with scores (time to execute) <= now.
18
+ until (jobs = conn.zrangebyscore(sorted_set, "-inf", now, limit: [0, 100])).empty?
19
+ # We need to go through the list one at a time to reduce the risk of something
20
+ # going wrong between the time jobs are popped from the scheduled queue and when
21
+ # they are pushed onto a work queue and losing the jobs.
22
+ jobs.each do |job|
23
+ # Pop item off the queue and add it to the work queue. If the job can't be popped from
24
+ # the queue, it's because another process already popped it so we can move on to the
25
+ # next one.
26
+ if conn.zrem(sorted_set, job)
27
+ Sidekiq::Client.push(Sidekiq.load_json(job))
28
+ Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
29
+ end
28
30
  end
29
31
  end
30
32
  end
@@ -61,26 +63,24 @@ module Sidekiq
61
63
  end
62
64
 
63
65
  def start
64
- @thread ||= safe_thread("scheduler") do
66
+ @thread ||= safe_thread("scheduler") {
65
67
  initial_wait
66
68
 
67
- while !@done
69
+ until @done
68
70
  enqueue
69
71
  wait
70
72
  end
71
73
  Sidekiq.logger.info("Scheduler exiting...")
72
- end
74
+ }
73
75
  end
74
76
 
75
77
  def enqueue
76
- begin
77
- @enq.enqueue_jobs
78
- rescue => ex
79
- # Most likely a problem with redis networking.
80
- # Punt and try again at the next interval
81
- logger.error ex.message
82
- handle_exception(ex)
83
- end
78
+ @enq.enqueue_jobs
79
+ rescue => ex
80
+ # Most likely a problem with redis networking.
81
+ # Punt and try again at the next interval
82
+ logger.error ex.message
83
+ handle_exception(ex)
84
84
  end
85
85
 
86
86
  private
@@ -168,7 +168,6 @@ module Sidekiq
168
168
  @sleeper.pop(total)
169
169
  rescue Timeout::Error
170
170
  end
171
-
172
171
  end
173
172
  end
174
173
  end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The MIT License
4
+ #
5
+ # Copyright (c) 2017, 2018, 2019, 2020 Agis Anastasopoulos
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
8
+ # this software and associated documentation files (the "Software"), to deal in
9
+ # the Software without restriction, including without limitation the rights to
10
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11
+ # the Software, and to permit persons to whom the Software is furnished to do so,
12
+ # subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in all
15
+ # copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ # This is a copy of https://github.com/agis/ruby-sdnotify as of commit a7d52ee
25
+ # The only changes made was "rehoming" it within the Sidekiq module to avoid
26
+ # namespace collisions and applying standard's code formatting style.
27
+
28
+ require "socket"
29
+
30
+ # SdNotify is a pure-Ruby implementation of sd_notify(3). It can be used to
31
+ # notify systemd about state changes. Methods of this package are no-op on
32
+ # non-systemd systems (eg. Darwin).
33
+ #
34
+ # The API maps closely to the original implementation of sd_notify(3),
35
+ # therefore be sure to check the official man pages prior to using SdNotify.
36
+ #
37
+ # @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
38
+ module Sidekiq
39
+ module SdNotify
40
+ # Exception raised when there's an error writing to the notification socket
41
+ class NotifyError < RuntimeError; end
42
+
43
+ READY = "READY=1"
44
+ RELOADING = "RELOADING=1"
45
+ STOPPING = "STOPPING=1"
46
+ STATUS = "STATUS="
47
+ ERRNO = "ERRNO="
48
+ MAINPID = "MAINPID="
49
+ WATCHDOG = "WATCHDOG=1"
50
+ FDSTORE = "FDSTORE=1"
51
+
52
+ def self.ready(unset_env = false)
53
+ notify(READY, unset_env)
54
+ end
55
+
56
+ def self.reloading(unset_env = false)
57
+ notify(RELOADING, unset_env)
58
+ end
59
+
60
+ def self.stopping(unset_env = false)
61
+ notify(STOPPING, unset_env)
62
+ end
63
+
64
+ # @param status [String] a custom status string that describes the current
65
+ # state of the service
66
+ def self.status(status, unset_env = false)
67
+ notify("#{STATUS}#{status}", unset_env)
68
+ end
69
+
70
+ # @param errno [Integer]
71
+ def self.errno(errno, unset_env = false)
72
+ notify("#{ERRNO}#{errno}", unset_env)
73
+ end
74
+
75
+ # @param pid [Integer]
76
+ def self.mainpid(pid, unset_env = false)
77
+ notify("#{MAINPID}#{pid}", unset_env)
78
+ end
79
+
80
+ def self.watchdog(unset_env = false)
81
+ notify(WATCHDOG, unset_env)
82
+ end
83
+
84
+ def self.fdstore(unset_env = false)
85
+ notify(FDSTORE, unset_env)
86
+ end
87
+
88
+ # @return [Boolean] true if the service manager expects watchdog keep-alive
89
+ # notification messages to be sent from this process.
90
+ #
91
+ # If the $WATCHDOG_USEC environment variable is set,
92
+ # and the $WATCHDOG_PID variable is unset or set to the PID of the current
93
+ # process
94
+ #
95
+ # @note Unlike sd_watchdog_enabled(3), this method does not mutate the
96
+ # environment.
97
+ def self.watchdog?
98
+ wd_usec = ENV["WATCHDOG_USEC"]
99
+ wd_pid = ENV["WATCHDOG_PID"]
100
+
101
+ return false unless wd_usec
102
+
103
+ begin
104
+ wd_usec = Integer(wd_usec)
105
+ rescue
106
+ return false
107
+ end
108
+
109
+ return false if wd_usec <= 0
110
+ return true if !wd_pid || wd_pid == $$.to_s
111
+
112
+ false
113
+ end
114
+
115
+ # Notify systemd with the provided state, via the notification socket, if
116
+ # any.
117
+ #
118
+ # Generally this method will be used indirectly through the other methods
119
+ # of the library.
120
+ #
121
+ # @param state [String]
122
+ # @param unset_env [Boolean]
123
+ #
124
+ # @return [Fixnum, nil] the number of bytes written to the notification
125
+ # socket or nil if there was no socket to report to (eg. the program wasn't
126
+ # started by systemd)
127
+ #
128
+ # @raise [NotifyError] if there was an error communicating with the systemd
129
+ # socket
130
+ #
131
+ # @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
132
+ def self.notify(state, unset_env = false)
133
+ sock = ENV["NOTIFY_SOCKET"]
134
+
135
+ return nil unless sock
136
+
137
+ ENV.delete("NOTIFY_SOCKET") if unset_env
138
+
139
+ begin
140
+ Addrinfo.unix(sock, :DGRAM).connect do |s|
141
+ s.close_on_exec = true
142
+ s.write(state)
143
+ end
144
+ rescue => e
145
+ raise NotifyError, "#{e.class}: #{e.message}", e.backtrace
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,24 @@
1
+ #
2
+ # Sidekiq's systemd integration allows Sidekiq to inform systemd:
3
+ # 1. when it has successfully started
4
+ # 2. when it is starting shutdown
5
+ # 3. periodically for a liveness check with a watchdog thread
6
+ #
7
+ module Sidekiq
8
+ def self.start_watchdog
9
+ usec = Integer(ENV["WATCHDOG_USEC"])
10
+ return Sidekiq.logger.error("systemd Watchdog too fast: " + usec) if usec < 1_000_000
11
+
12
+ sec_f = usec / 1_000_000.0
13
+ # "It is recommended that a daemon sends a keep-alive notification message
14
+ # to the service manager every half of the time returned here."
15
+ ping_f = sec_f / 2
16
+ Sidekiq.logger.info "Pinging systemd watchdog every #{ping_f.round(1)} sec"
17
+ Thread.new do
18
+ loop do
19
+ sleep ping_f
20
+ Sidekiq::SdNotify.watchdog
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
- require 'sidekiq/testing'
2
+
3
+ require "sidekiq/testing"
3
4
 
4
5
  ##
5
6
  # The Sidekiq inline infrastructure overrides perform_async so that it
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
- require 'securerandom'
3
- require 'sidekiq'
4
2
 
5
- module Sidekiq
3
+ require "securerandom"
4
+ require "sidekiq"
6
5
 
6
+ module Sidekiq
7
7
  class Testing
8
8
  class << self
9
9
  attr_accessor :__test_mode
10
10
 
11
11
  def __set_test_mode(mode)
12
12
  if block_given?
13
- current_mode = self.__test_mode
13
+ current_mode = __test_mode
14
14
  begin
15
15
  self.__test_mode = mode
16
16
  yield
@@ -35,19 +35,19 @@ module Sidekiq
35
35
  end
36
36
 
37
37
  def enabled?
38
- self.__test_mode != :disable
38
+ __test_mode != :disable
39
39
  end
40
40
 
41
41
  def disabled?
42
- self.__test_mode == :disable
42
+ __test_mode == :disable
43
43
  end
44
44
 
45
45
  def fake?
46
- self.__test_mode == :fake
46
+ __test_mode == :fake
47
47
  end
48
48
 
49
49
  def inline?
50
- self.__test_mode == :inline
50
+ __test_mode == :inline
51
51
  end
52
52
 
53
53
  def server_middleware
@@ -57,7 +57,7 @@ module Sidekiq
57
57
  end
58
58
 
59
59
  def constantize(str)
60
- names = str.split('::')
60
+ names = str.split("::")
61
61
  names.shift if names.empty? || names.first.empty?
62
62
 
63
63
  names.inject(Object) do |constant, name|
@@ -77,14 +77,14 @@ module Sidekiq
77
77
  if Sidekiq::Testing.fake?
78
78
  payloads.each do |job|
79
79
  job = Sidekiq.load_json(Sidekiq.dump_json(job))
80
- job.merge!('enqueued_at' => Time.now.to_f) unless job['at']
81
- Queues.push(job['queue'], job['class'], job)
80
+ job["enqueued_at"] = Time.now.to_f unless job["at"]
81
+ Queues.push(job["queue"], job["class"], job)
82
82
  end
83
83
  true
84
84
  elsif Sidekiq::Testing.inline?
85
85
  payloads.each do |job|
86
- klass = Sidekiq::Testing.constantize(job['class'])
87
- job['id'] ||= SecureRandom.hex(12)
86
+ klass = Sidekiq::Testing.constantize(job["class"])
87
+ job["id"] ||= SecureRandom.hex(12)
88
88
  job_hash = Sidekiq.load_json(Sidekiq.dump_json(job))
89
89
  klass.process_job(job_hash)
90
90
  end
@@ -255,27 +255,26 @@ module Sidekiq
255
255
  # Then I should receive a welcome email to "foo@example.com"
256
256
  #
257
257
  module ClassMethods
258
-
259
258
  # Queue for this worker
260
259
  def queue
261
- self.sidekiq_options["queue"]
260
+ get_sidekiq_options["queue"]
262
261
  end
263
262
 
264
263
  # Jobs queued for this worker
265
264
  def jobs
266
- Queues.jobs_by_worker[self.to_s]
265
+ Queues.jobs_by_worker[to_s]
267
266
  end
268
267
 
269
268
  # Clear all jobs for this worker
270
269
  def clear
271
- Queues.clear_for(queue, self.to_s)
270
+ Queues.clear_for(queue, to_s)
272
271
  end
273
272
 
274
273
  # Drain and run all jobs for this worker
275
274
  def drain
276
275
  while jobs.any?
277
276
  next_job = jobs.first
278
- Queues.delete_for(next_job["jid"], next_job["queue"], self.to_s)
277
+ Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
279
278
  process_job(next_job)
280
279
  end
281
280
  end
@@ -284,16 +283,16 @@ module Sidekiq
284
283
  def perform_one
285
284
  raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
286
285
  next_job = jobs.first
287
- Queues.delete_for(next_job["jid"], queue, self.to_s)
286
+ Queues.delete_for(next_job["jid"], queue, to_s)
288
287
  process_job(next_job)
289
288
  end
290
289
 
291
290
  def process_job(job)
292
291
  worker = new
293
- worker.jid = job['jid']
294
- worker.bid = job['bid'] if worker.respond_to?(:bid=)
295
- Sidekiq::Testing.server_middleware.invoke(worker, job, job['queue']) do
296
- execute_job(worker, job['args'])
292
+ worker.jid = job["jid"]
293
+ worker.bid = job["bid"] if worker.respond_to?(:bid=)
294
+ Sidekiq::Testing.server_middleware.invoke(worker, job, job["queue"]) do
295
+ execute_job(worker, job["args"])
297
296
  end
298
297
  end
299
298
 
@@ -324,9 +323,21 @@ module Sidekiq
324
323
  end
325
324
  end
326
325
  end
326
+
327
+ module TestingExtensions
328
+ def jobs_for(klass)
329
+ jobs.select do |job|
330
+ marshalled = job["args"][0]
331
+ marshalled.index(klass.to_s) && YAML.load(marshalled)[0] == klass
332
+ end
333
+ end
334
+ end
335
+
336
+ Sidekiq::Extensions::DelayedMailer.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedMailer)
337
+ Sidekiq::Extensions::DelayedModel.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedModel)
327
338
  end
328
339
 
329
- if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test?
340
+ if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING
330
341
  puts("**************************************************")
331
342
  puts("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.")
332
343
  puts("**************************************************")