sidekiq 6.0.1 → 6.0.6

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.

@@ -16,7 +16,7 @@ module Sidekiq
16
16
  proc { Sidekiq::VERSION },
17
17
  proc { |me, data| data["tag"] },
18
18
  proc { |me, data| "[#{Processor::WORKER_STATE.size} of #{data["concurrency"]} busy]" },
19
- proc { |me, data| "stopping" if me.stopping? },
19
+ proc { |me, data| "stopping" if me.stopping? }
20
20
  ]
21
21
 
22
22
  attr_accessor :manager, :poller, :fetcher
@@ -83,7 +83,7 @@ module Sidekiq
83
83
  Sidekiq.redis do |conn|
84
84
  conn.pipelined do
85
85
  conn.srem("processes", identity)
86
- conn.del("#{identity}:workers")
86
+ conn.unlink("#{identity}:workers")
87
87
  end
88
88
  end
89
89
  rescue
@@ -96,6 +96,24 @@ module Sidekiq
96
96
 
97
97
  end
98
98
 
99
+ def self.flush_stats
100
+ nowdate = Time.now.utc.strftime("%Y-%m-%d")
101
+ fails = Processor::FAILURE.reset
102
+ procd = Processor::PROCESSED.reset
103
+ Sidekiq.redis do |conn|
104
+ conn.pipelined do
105
+ conn.incrby("stat:processed", procd)
106
+ conn.incrby("stat:processed:#{nowdate}", procd)
107
+ conn.expire("stat:processed:#{nowdate}", STATS_TTL)
108
+
109
+ conn.incrby("stat:failed", fails)
110
+ conn.incrby("stat:failed:#{nowdate}", fails)
111
+ conn.expire("stat:failed:#{nowdate}", STATS_TTL)
112
+ end
113
+ end
114
+ end
115
+ at_exit(&method(:flush_stats))
116
+
99
117
  def ❤
100
118
  key = identity
101
119
  fails = procd = 0
@@ -118,7 +136,7 @@ module Sidekiq
118
136
  conn.incrby("stat:failed:#{nowdate}", fails)
119
137
  conn.expire("stat:failed:#{nowdate}", STATS_TTL)
120
138
 
121
- conn.del(workers_key)
139
+ conn.unlink(workers_key)
122
140
  curstate.each_pair do |tid, hash|
123
141
  conn.hset(workers_key, tid, Sidekiq.dump_json(hash))
124
142
  end
@@ -163,7 +181,7 @@ module Sidekiq
163
181
  "concurrency" => @options[:concurrency],
164
182
  "queues" => @options[:queues].uniq,
165
183
  "labels" => @options[:labels],
166
- "identity" => identity,
184
+ "identity" => identity
167
185
  }
168
186
  end
169
187
  end
@@ -23,7 +23,7 @@ module Sidekiq
23
23
  "info" => 1,
24
24
  "warn" => 2,
25
25
  "error" => 3,
26
- "fatal" => 4,
26
+ "fatal" => 4
27
27
  }
28
28
  LEVELS.default_proc = proc do |_, level|
29
29
  Sidekiq.logger.warn("Invalid log level: #{level.inspect}")
@@ -31,23 +31,23 @@ module Sidekiq
31
31
  end
32
32
 
33
33
  def debug?
34
- level >= 0
34
+ level <= 0
35
35
  end
36
36
 
37
37
  def info?
38
- level >= 1
38
+ level <= 1
39
39
  end
40
40
 
41
41
  def warn?
42
- level >= 2
42
+ level <= 2
43
43
  end
44
44
 
45
45
  def error?
46
- level >= 3
46
+ level <= 3
47
47
  end
48
48
 
49
49
  def fatal?
50
- level >= 4
50
+ level <= 4
51
51
  end
52
52
 
53
53
  def local_level
@@ -83,7 +83,7 @@ module Sidekiq
83
83
  # Redefined to check severity against #level, and thus the thread-local level, rather than +@level+.
84
84
  # FIXME: Remove when the minimum Ruby version supports overriding Logger#level.
85
85
  def add(severity, message = nil, progname = nil, &block)
86
- severity ||= UNKNOWN
86
+ severity ||= ::Logger::UNKNOWN
87
87
  progname ||= @progname
88
88
 
89
89
  return true if @logdev.nil? || severity < level
@@ -104,7 +104,7 @@ module Sidekiq
104
104
  class Logger < ::Logger
105
105
  include LoggingUtils
106
106
 
107
- def initialize(*args)
107
+ def initialize(*args, **kwargs)
108
108
  super
109
109
  self.formatter = Sidekiq.log_formatter
110
110
  end
@@ -152,7 +152,7 @@ module Sidekiq
152
152
  pid: ::Process.pid,
153
153
  tid: tid,
154
154
  lvl: severity,
155
- msg: message,
155
+ msg: message
156
156
  }
157
157
  c = ctx
158
158
  hash["ctx"] = c unless c.empty?
@@ -4,21 +4,6 @@ require "fileutils"
4
4
  require "sidekiq/api"
5
5
 
6
6
  class Sidekiq::Monitor
7
- CMD = File.basename($PROGRAM_NAME)
8
-
9
- attr_reader :stage
10
-
11
- def self.print_usage
12
- puts "#{CMD} - monitor Sidekiq from the command line."
13
- puts
14
- puts "Usage: #{CMD} <section>"
15
- puts
16
- puts " <section> (optional) view a specific section of the status output"
17
- puts " Valid sections are: #{Sidekiq::Monitor::Status::VALID_SECTIONS.join(", ")}"
18
- puts
19
- puts "Set REDIS_URL to the location of your Redis server if not monitoring localhost."
20
- end
21
-
22
7
  class Status
23
8
  VALID_SECTIONS = %w[all version overview processes queues]
24
9
  COL_PAD = 2
@@ -77,7 +62,7 @@ class Sidekiq::Monitor
77
62
  columns = {
78
63
  name: [:ljust, (["name"] + queue_data.map(&:name)).map(&:length).max + COL_PAD],
79
64
  size: [:rjust, (["size"] + queue_data.map(&:size)).map(&:length).max + COL_PAD],
80
- latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD],
65
+ latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD]
81
66
  }
82
67
  columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) }
83
68
  puts
@@ -116,7 +101,7 @@ class Sidekiq::Monitor
116
101
  tags = [
117
102
  process["tag"],
118
103
  process["labels"],
119
- (process["quiet"] == "true" ? "quiet" : nil),
104
+ (process["quiet"] == "true" ? "quiet" : nil)
120
105
  ].flatten.compact
121
106
  tags.any? ? "[#{tags.join("] [")}]" : nil
122
107
  end
@@ -111,16 +111,19 @@ module Sidekiq
111
111
  nil
112
112
  end
113
113
 
114
- def dispatch(job_hash, queue)
114
+ def dispatch(job_hash, queue, jobstr)
115
115
  # since middleware can mutate the job hash
116
- # we clone here so we report the original
116
+ # we need to clone it to report the original
117
117
  # job structure to the Web UI
118
- pristine = json_clone(job_hash)
118
+ # or to push back to redis when retrying.
119
+ # To avoid costly and, most of the time, useless cloning here,
120
+ # we pass original String of JSON to respected methods
121
+ # to re-parse it there if we need access to the original, untouched job
119
122
 
120
123
  @job_logger.prepare(job_hash) do
121
- @retrier.global(pristine, queue) do
124
+ @retrier.global(jobstr, queue) do
122
125
  @job_logger.call(job_hash, queue) do
123
- stats(pristine, queue) do
126
+ stats(jobstr, queue) do
124
127
  # Rails 5 requires a Reloader to wrap code execution. In order to
125
128
  # constantize the worker and instantiate an instance, we have to call
126
129
  # the Reloader. It handles code loading, db connection management, etc.
@@ -129,7 +132,7 @@ module Sidekiq
129
132
  klass = constantize(job_hash["class"])
130
133
  worker = klass.new
131
134
  worker.jid = job_hash["jid"]
132
- @retrier.local(worker, pristine, queue) do
135
+ @retrier.local(worker, jobstr, queue) do
133
136
  yield worker
134
137
  end
135
138
  end
@@ -156,7 +159,7 @@ module Sidekiq
156
159
 
157
160
  ack = false
158
161
  begin
159
- dispatch(job_hash, queue) do |worker|
162
+ dispatch(job_hash, queue, jobstr) do |worker|
160
163
  Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
161
164
  execute_job(worker, job_hash["args"])
162
165
  end
@@ -178,7 +181,7 @@ module Sidekiq
178
181
  # the retry subsystem (e.g. network partition). We won't acknowledge the job
179
182
  # so it can be rescued when using Sidekiq Pro.
180
183
  handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
181
- raise e
184
+ raise ex
182
185
  ensure
183
186
  if ack
184
187
  # We don't want a shutdown signal to interrupt job acknowledgment.
@@ -247,8 +250,8 @@ module Sidekiq
247
250
  FAILURE = Counter.new
248
251
  WORKER_STATE = SharedWorkerState.new
249
252
 
250
- def stats(job_hash, queue)
251
- WORKER_STATE.set(tid, {queue: queue, payload: job_hash, run_at: Time.now.to_i})
253
+ def stats(jobstr, queue)
254
+ WORKER_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
252
255
 
253
256
  begin
254
257
  yield
@@ -273,30 +276,5 @@ module Sidekiq
273
276
  constant.const_get(name, false)
274
277
  end
275
278
  end
276
-
277
- # Deep clone the arguments passed to the worker so that if
278
- # the job fails, what is pushed back onto Redis hasn't
279
- # been mutated by the worker.
280
- def json_clone(obj)
281
- if Integer === obj || Float === obj || TrueClass === obj || FalseClass === obj || NilClass === obj
282
- return obj
283
- elsif String === obj
284
- return obj.dup
285
- elsif Array === obj
286
- duped = Array.new(obj.size)
287
- obj.each_with_index do |value, index|
288
- duped[index] = json_clone(value)
289
- end
290
- elsif Hash === obj
291
- duped = obj.dup
292
- duped.each_pair do |key, value|
293
- duped[key] = json_clone(value)
294
- end
295
- else
296
- duped = obj.dup
297
- end
298
-
299
- duped
300
- end
301
279
  end
302
280
  end
@@ -103,6 +103,9 @@ module Sidekiq
103
103
  if scrubbed_options[:password]
104
104
  scrubbed_options[:password] = redacted
105
105
  end
106
+ scrubbed_options[:sentinels]&.each do |sentinel|
107
+ sentinel[:password] = redacted if sentinel[:password]
108
+ end
106
109
  if Sidekiq.server?
107
110
  Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} with redis options #{scrubbed_options}")
108
111
  else
@@ -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
+ # @param [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,38 @@
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
+ Sidekiq::SdNotify.watchdog
20
+ sleep ping_f
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ if ENV["NOTIFY_SOCKET"]
27
+ Sidekiq.configure_server do |config|
28
+ Sidekiq.logger.info "Enabling systemd notification integration"
29
+ require "sidekiq/sd_notify"
30
+ config.on(:startup) do
31
+ Sidekiq::SdNotify.ready
32
+ end
33
+ config.on(:shutdown) do
34
+ Sidekiq::SdNotify.stopping
35
+ end
36
+ Sidekiq.start_watchdog if Sidekiq::SdNotify.watchdog?
37
+ end
38
+ end