sidekiq 5.2.8 → 6.2.2

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +248 -0
  3. data/LICENSE +1 -1
  4. data/README.md +18 -34
  5. data/bin/sidekiq +26 -2
  6. data/bin/sidekiqload +32 -24
  7. data/bin/sidekiqmon +8 -0
  8. data/lib/generators/sidekiq/templates/worker_test.rb.erb +1 -1
  9. data/lib/generators/sidekiq/worker_generator.rb +21 -13
  10. data/lib/sidekiq/api.rb +310 -249
  11. data/lib/sidekiq/cli.rb +144 -180
  12. data/lib/sidekiq/client.rb +64 -48
  13. data/lib/sidekiq/delay.rb +5 -6
  14. data/lib/sidekiq/exception_handler.rb +10 -12
  15. data/lib/sidekiq/extensions/action_mailer.rb +13 -22
  16. data/lib/sidekiq/extensions/active_record.rb +13 -10
  17. data/lib/sidekiq/extensions/class_methods.rb +14 -11
  18. data/lib/sidekiq/extensions/generic_proxy.rb +6 -4
  19. data/lib/sidekiq/fetch.rb +38 -31
  20. data/lib/sidekiq/job.rb +8 -0
  21. data/lib/sidekiq/job_logger.rb +45 -7
  22. data/lib/sidekiq/job_retry.rb +64 -67
  23. data/lib/sidekiq/launcher.rb +146 -60
  24. data/lib/sidekiq/logger.rb +166 -0
  25. data/lib/sidekiq/manager.rb +11 -13
  26. data/lib/sidekiq/middleware/chain.rb +20 -8
  27. data/lib/sidekiq/middleware/i18n.rb +5 -7
  28. data/lib/sidekiq/monitor.rb +133 -0
  29. data/lib/sidekiq/paginator.rb +18 -14
  30. data/lib/sidekiq/processor.rb +71 -70
  31. data/lib/sidekiq/rails.rb +29 -37
  32. data/lib/sidekiq/redis_connection.rb +50 -48
  33. data/lib/sidekiq/scheduled.rb +35 -30
  34. data/lib/sidekiq/sd_notify.rb +149 -0
  35. data/lib/sidekiq/systemd.rb +24 -0
  36. data/lib/sidekiq/testing/inline.rb +2 -1
  37. data/lib/sidekiq/testing.rb +36 -27
  38. data/lib/sidekiq/util.rb +45 -16
  39. data/lib/sidekiq/version.rb +2 -1
  40. data/lib/sidekiq/web/action.rb +15 -11
  41. data/lib/sidekiq/web/application.rb +86 -76
  42. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  43. data/lib/sidekiq/web/helpers.rb +114 -86
  44. data/lib/sidekiq/web/router.rb +23 -19
  45. data/lib/sidekiq/web.rb +61 -105
  46. data/lib/sidekiq/worker.rb +126 -102
  47. data/lib/sidekiq.rb +69 -44
  48. data/sidekiq.gemspec +23 -16
  49. data/web/assets/images/apple-touch-icon.png +0 -0
  50. data/web/assets/javascripts/application.js +25 -27
  51. data/web/assets/javascripts/dashboard.js +4 -23
  52. data/web/assets/stylesheets/application-dark.css +147 -0
  53. data/web/assets/stylesheets/application.css +37 -128
  54. data/web/locales/ar.yml +8 -2
  55. data/web/locales/de.yml +14 -2
  56. data/web/locales/en.yml +5 -0
  57. data/web/locales/es.yml +18 -2
  58. data/web/locales/fr.yml +10 -3
  59. data/web/locales/ja.yml +7 -1
  60. data/web/locales/lt.yml +83 -0
  61. data/web/locales/pl.yml +4 -4
  62. data/web/locales/ru.yml +4 -0
  63. data/web/locales/vi.yml +83 -0
  64. data/web/views/_job_info.erb +3 -2
  65. data/web/views/busy.erb +54 -20
  66. data/web/views/dashboard.erb +14 -6
  67. data/web/views/dead.erb +3 -3
  68. data/web/views/layout.erb +2 -0
  69. data/web/views/morgue.erb +9 -6
  70. data/web/views/queue.erb +11 -2
  71. data/web/views/queues.erb +10 -2
  72. data/web/views/retries.erb +11 -8
  73. data/web/views/retry.erb +3 -3
  74. data/web/views/scheduled.erb +5 -2
  75. metadata +32 -64
  76. data/.circleci/config.yml +0 -61
  77. data/.github/contributing.md +0 -32
  78. data/.github/issue_template.md +0 -11
  79. data/.gitignore +0 -15
  80. data/.travis.yml +0 -11
  81. data/3.0-Upgrade.md +0 -70
  82. data/4.0-Upgrade.md +0 -53
  83. data/5.0-Upgrade.md +0 -56
  84. data/COMM-LICENSE +0 -97
  85. data/Ent-Changes.md +0 -238
  86. data/Gemfile +0 -23
  87. data/Pro-2.0-Upgrade.md +0 -138
  88. data/Pro-3.0-Upgrade.md +0 -44
  89. data/Pro-4.0-Upgrade.md +0 -35
  90. data/Pro-Changes.md +0 -759
  91. data/Rakefile +0 -9
  92. data/bin/sidekiqctl +0 -20
  93. data/code_of_conduct.md +0 -50
  94. data/lib/sidekiq/core_ext.rb +0 -1
  95. data/lib/sidekiq/ctl.rb +0 -221
  96. data/lib/sidekiq/logging.rb +0 -122
  97. data/lib/sidekiq/middleware/server/active_record.rb +0 -23
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "time"
5
+
6
+ module Sidekiq
7
+ module Context
8
+ def self.with(hash)
9
+ orig_context = current.dup
10
+ current.merge!(hash)
11
+ yield
12
+ ensure
13
+ Thread.current[:sidekiq_context] = orig_context
14
+ end
15
+
16
+ def self.current
17
+ Thread.current[:sidekiq_context] ||= {}
18
+ end
19
+ end
20
+
21
+ module LoggingUtils
22
+ LEVELS = {
23
+ "debug" => 0,
24
+ "info" => 1,
25
+ "warn" => 2,
26
+ "error" => 3,
27
+ "fatal" => 4
28
+ }
29
+ LEVELS.default_proc = proc do |_, level|
30
+ Sidekiq.logger.warn("Invalid log level: #{level.inspect}")
31
+ nil
32
+ end
33
+
34
+ def debug?
35
+ level <= 0
36
+ end
37
+
38
+ def info?
39
+ level <= 1
40
+ end
41
+
42
+ def warn?
43
+ level <= 2
44
+ end
45
+
46
+ def error?
47
+ level <= 3
48
+ end
49
+
50
+ def fatal?
51
+ level <= 4
52
+ end
53
+
54
+ def local_level
55
+ Thread.current[:sidekiq_log_level]
56
+ end
57
+
58
+ def local_level=(level)
59
+ case level
60
+ when Integer
61
+ Thread.current[:sidekiq_log_level] = level
62
+ when Symbol, String
63
+ Thread.current[:sidekiq_log_level] = LEVELS[level.to_s]
64
+ when nil
65
+ Thread.current[:sidekiq_log_level] = nil
66
+ else
67
+ raise ArgumentError, "Invalid log level: #{level.inspect}"
68
+ end
69
+ end
70
+
71
+ def level
72
+ local_level || super
73
+ end
74
+
75
+ # Change the thread-local level for the duration of the given block.
76
+ def log_at(level)
77
+ old_local_level = local_level
78
+ self.local_level = level
79
+ yield
80
+ ensure
81
+ self.local_level = old_local_level
82
+ end
83
+
84
+ # Redefined to check severity against #level, and thus the thread-local level, rather than +@level+.
85
+ # FIXME: Remove when the minimum Ruby version supports overriding Logger#level.
86
+ def add(severity, message = nil, progname = nil, &block)
87
+ severity ||= ::Logger::UNKNOWN
88
+ progname ||= @progname
89
+
90
+ return true if @logdev.nil? || severity < level
91
+
92
+ if message.nil?
93
+ if block
94
+ message = yield
95
+ else
96
+ message = progname
97
+ progname = @progname
98
+ end
99
+ end
100
+
101
+ @logdev.write format_message(format_severity(severity), Time.now, progname, message)
102
+ end
103
+ end
104
+
105
+ class Logger < ::Logger
106
+ include LoggingUtils
107
+
108
+ def initialize(*args, **kwargs)
109
+ super
110
+ self.formatter = Sidekiq.log_formatter
111
+ end
112
+
113
+ module Formatters
114
+ class Base < ::Logger::Formatter
115
+ def tid
116
+ Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
117
+ end
118
+
119
+ def ctx
120
+ Sidekiq::Context.current
121
+ end
122
+
123
+ def format_context
124
+ if ctx.any?
125
+ " " + ctx.compact.map { |k, v|
126
+ case v
127
+ when Array
128
+ "#{k}=#{v.join(",")}"
129
+ else
130
+ "#{k}=#{v}"
131
+ end
132
+ }.join(" ")
133
+ end
134
+ end
135
+ end
136
+
137
+ class Pretty < Base
138
+ def call(severity, time, program_name, message)
139
+ "#{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context} #{severity}: #{message}\n"
140
+ end
141
+ end
142
+
143
+ class WithoutTimestamp < Pretty
144
+ def call(severity, time, program_name, message)
145
+ "pid=#{::Process.pid} tid=#{tid}#{format_context} #{severity}: #{message}\n"
146
+ end
147
+ end
148
+
149
+ class JSON < Base
150
+ def call(severity, time, program_name, message)
151
+ hash = {
152
+ ts: time.utc.iso8601(3),
153
+ pid: ::Process.pid,
154
+ tid: tid,
155
+ lvl: severity,
156
+ msg: message
157
+ }
158
+ c = ctx
159
+ hash["ctx"] = c unless c.empty?
160
+
161
+ Sidekiq.dump_json(hash) << "\n"
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -1,12 +1,11 @@
1
1
  # frozen_string_literal: true
2
- require 'sidekiq/util'
3
- require 'sidekiq/processor'
4
- require 'sidekiq/fetch'
5
- require 'thread'
6
- require 'set'
7
2
 
8
- module Sidekiq
3
+ require "sidekiq/util"
4
+ require "sidekiq/processor"
5
+ require "sidekiq/fetch"
6
+ require "set"
9
7
 
8
+ module Sidekiq
10
9
  ##
11
10
  # The Manager is the central coordination point in Sidekiq, controlling
12
11
  # the lifecycle of the Processors.
@@ -27,7 +26,7 @@ module Sidekiq
27
26
  attr_reader :workers
28
27
  attr_reader :options
29
28
 
30
- def initialize(options={})
29
+ def initialize(options = {})
31
30
  logger.debug { options.inspect }
32
31
  @options = options
33
32
  @count = options[:concurrency] || 10
@@ -36,7 +35,7 @@ module Sidekiq
36
35
  @done = false
37
36
  @workers = Set.new
38
37
  @count.times do
39
- @workers << Processor.new(self)
38
+ @workers << Processor.new(self, options)
40
39
  end
41
40
  @plock = Mutex.new
42
41
  end
@@ -57,7 +56,7 @@ module Sidekiq
57
56
  end
58
57
 
59
58
  # hack for quicker development / testing environment #2774
60
- PAUSE_TIME = STDOUT.tty? ? 0.1 : 0.5
59
+ PAUSE_TIME = $stdout.tty? ? 0.1 : 0.5
61
60
 
62
61
  def stop(deadline)
63
62
  quiet
@@ -91,7 +90,7 @@ module Sidekiq
91
90
  @plock.synchronize do
92
91
  @workers.delete(processor)
93
92
  unless @done
94
- p = Processor.new(self)
93
+ p = Processor.new(self, options)
95
94
  @workers << p
96
95
  p.start
97
96
  end
@@ -113,7 +112,7 @@ module Sidekiq
113
112
  end
114
113
 
115
114
  if cleanup.size > 0
116
- jobs = cleanup.map {|p| p.job }.compact
115
+ jobs = cleanup.map { |p| p.job }.compact
117
116
 
118
117
  logger.warn { "Terminating #{cleanup.size} busy worker threads" }
119
118
  logger.warn { "Work still in progress #{jobs.inspect}" }
@@ -124,7 +123,7 @@ module Sidekiq
124
123
  # contract says that jobs are run AT LEAST once. Process termination
125
124
  # is delayed until we're certain the jobs are back in Redis because
126
125
  # it is worse to lose a job than to run it twice.
127
- strategy = (@options[:fetch] || Sidekiq::BasicFetch)
126
+ strategy = @options[:fetch]
128
127
  strategy.bulk_requeue(jobs, @options)
129
128
  end
130
129
 
@@ -132,6 +131,5 @@ module Sidekiq
132
131
  processor.kill
133
132
  end
134
133
  end
135
-
136
134
  end
137
135
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Sidekiq
3
4
  # Middleware is code configured to run before/after
4
5
  # a message is processed. It is patterned after Rack
@@ -66,7 +67,6 @@ module Sidekiq
66
67
  module Middleware
67
68
  class Chain
68
69
  include Enumerable
69
- attr_reader :entries
70
70
 
71
71
  def initialize_copy(copy)
72
72
  copy.instance_variable_set(:@entries, entries.dup)
@@ -77,21 +77,25 @@ module Sidekiq
77
77
  end
78
78
 
79
79
  def initialize
80
- @entries = []
80
+ @entries = nil
81
81
  yield self if block_given?
82
82
  end
83
83
 
84
+ def entries
85
+ @entries ||= []
86
+ end
87
+
84
88
  def remove(klass)
85
89
  entries.delete_if { |entry| entry.klass == klass }
86
90
  end
87
91
 
88
92
  def add(klass, *args)
89
- remove(klass) if exists?(klass)
93
+ remove(klass)
90
94
  entries << Entry.new(klass, *args)
91
95
  end
92
96
 
93
97
  def prepend(klass, *args)
94
- remove(klass) if exists?(klass)
98
+ remove(klass)
95
99
  entries.insert(0, Entry.new(klass, *args))
96
100
  end
97
101
 
@@ -106,13 +110,17 @@ module Sidekiq
106
110
  i = entries.index { |entry| entry.klass == newklass }
107
111
  new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
108
112
  i = entries.index { |entry| entry.klass == oldklass } || entries.count - 1
109
- entries.insert(i+1, new_entry)
113
+ entries.insert(i + 1, new_entry)
110
114
  end
111
115
 
112
116
  def exists?(klass)
113
117
  any? { |entry| entry.klass == klass }
114
118
  end
115
119
 
120
+ def empty?
121
+ @entries.nil? || @entries.empty?
122
+ end
123
+
116
124
  def retrieve
117
125
  map(&:make_new)
118
126
  end
@@ -122,8 +130,10 @@ module Sidekiq
122
130
  end
123
131
 
124
132
  def invoke(*args)
125
- chain = retrieve.dup
126
- traverse_chain = lambda do
133
+ return yield if empty?
134
+
135
+ chain = retrieve
136
+ traverse_chain = proc do
127
137
  if chain.empty?
128
138
  yield
129
139
  else
@@ -134,12 +144,14 @@ module Sidekiq
134
144
  end
135
145
  end
136
146
 
147
+ private
148
+
137
149
  class Entry
138
150
  attr_reader :klass
139
151
 
140
152
  def initialize(klass, *args)
141
153
  @klass = klass
142
- @args = args
154
+ @args = args
143
155
  end
144
156
 
145
157
  def make_new
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Simple middleware to save the current locale and restore it when the job executes.
4
5
  # Use it by requiring it in your initializer:
@@ -9,19 +10,16 @@ module Sidekiq::Middleware::I18n
9
10
  # Get the current locale and store it in the message
10
11
  # to be sent to Sidekiq.
11
12
  class Client
12
- def call(worker_class, msg, queue, redis_pool)
13
- msg['locale'] ||= I18n.locale
13
+ def call(_worker, msg, _queue, _redis)
14
+ msg["locale"] ||= I18n.locale
14
15
  yield
15
16
  end
16
17
  end
17
18
 
18
19
  # Pull the msg locale out and set the current thread to use it.
19
20
  class Server
20
- def call(worker, msg, queue)
21
- I18n.locale = msg['locale'] || I18n.default_locale
22
- yield
23
- ensure
24
- I18n.locale = I18n.default_locale
21
+ def call(_worker, msg, _queue, &block)
22
+ I18n.with_locale(msg.fetch("locale", I18n.default_locale), &block)
25
23
  end
26
24
  end
27
25
  end
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "fileutils"
4
+ require "sidekiq/api"
5
+
6
+ class Sidekiq::Monitor
7
+ class Status
8
+ VALID_SECTIONS = %w[all version overview processes queues]
9
+ COL_PAD = 2
10
+
11
+ def display(section = nil)
12
+ section ||= "all"
13
+ unless VALID_SECTIONS.include? section
14
+ puts "I don't know how to check the status of '#{section}'!"
15
+ puts "Try one of these: #{VALID_SECTIONS.join(", ")}"
16
+ return
17
+ end
18
+ send(section)
19
+ rescue => e
20
+ puts "Couldn't get status: #{e}"
21
+ end
22
+
23
+ def all
24
+ version
25
+ puts
26
+ overview
27
+ puts
28
+ processes
29
+ puts
30
+ queues
31
+ end
32
+
33
+ def version
34
+ puts "Sidekiq #{Sidekiq::VERSION}"
35
+ puts Time.now.utc
36
+ end
37
+
38
+ def overview
39
+ puts "---- Overview ----"
40
+ puts " Processed: #{delimit stats.processed}"
41
+ puts " Failed: #{delimit stats.failed}"
42
+ puts " Busy: #{delimit stats.workers_size}"
43
+ puts " Enqueued: #{delimit stats.enqueued}"
44
+ puts " Retries: #{delimit stats.retry_size}"
45
+ puts " Scheduled: #{delimit stats.scheduled_size}"
46
+ puts " Dead: #{delimit stats.dead_size}"
47
+ end
48
+
49
+ def processes
50
+ puts "---- Processes (#{process_set.size}) ----"
51
+ process_set.each_with_index do |process, index|
52
+ puts "#{process["identity"]} #{tags_for(process)}"
53
+ puts " Started: #{Time.at(process["started_at"])} (#{time_ago(process["started_at"])})"
54
+ puts " Threads: #{process["concurrency"]} (#{process["busy"]} busy)"
55
+ puts " Queues: #{split_multiline(process["queues"].sort, pad: 11)}"
56
+ puts "" unless (index + 1) == process_set.size
57
+ end
58
+ end
59
+
60
+ def queues
61
+ puts "---- Queues (#{queue_data.size}) ----"
62
+ columns = {
63
+ name: [:ljust, (["name"] + queue_data.map(&:name)).map(&:length).max + COL_PAD],
64
+ size: [:rjust, (["size"] + queue_data.map(&:size)).map(&:length).max + COL_PAD],
65
+ latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD]
66
+ }
67
+ columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) }
68
+ puts
69
+ queue_data.each do |q|
70
+ columns.each do |col, (dir, width)|
71
+ print q.send(col).public_send(dir, width)
72
+ end
73
+ puts
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def delimit(number)
80
+ number.to_s.reverse.scan(/.{1,3}/).join(",").reverse
81
+ end
82
+
83
+ def split_multiline(values, opts = {})
84
+ return "none" unless values
85
+ pad = opts[:pad] || 0
86
+ max_length = opts[:max_length] || (80 - pad)
87
+ out = []
88
+ line = ""
89
+ values.each do |value|
90
+ if (line.length + value.length) > max_length
91
+ out << line
92
+ line = " " * pad
93
+ end
94
+ line << value + ", "
95
+ end
96
+ out << line[0..-3]
97
+ out.join("\n")
98
+ end
99
+
100
+ def tags_for(process)
101
+ tags = [
102
+ process["tag"],
103
+ process["labels"],
104
+ (process["quiet"] == "true" ? "quiet" : nil)
105
+ ].flatten.compact
106
+ tags.any? ? "[#{tags.join("] [")}]" : nil
107
+ end
108
+
109
+ def time_ago(timestamp)
110
+ seconds = Time.now - Time.at(timestamp)
111
+ return "just now" if seconds < 60
112
+ return "a minute ago" if seconds < 120
113
+ return "#{seconds.floor / 60} minutes ago" if seconds < 3600
114
+ return "an hour ago" if seconds < 7200
115
+ "#{seconds.floor / 60 / 60} hours ago"
116
+ end
117
+
118
+ QUEUE_STRUCT = Struct.new(:name, :size, :latency)
119
+ def queue_data
120
+ @queue_data ||= Sidekiq::Queue.all.map { |q|
121
+ QUEUE_STRUCT.new(q.name, q.size.to_s, sprintf("%#.2f", q.latency))
122
+ }
123
+ end
124
+
125
+ def process_set
126
+ @process_set ||= Sidekiq::ProcessSet.new
127
+ end
128
+
129
+ def stats
130
+ @stats ||= Sidekiq::Stats.new
131
+ end
132
+ end
133
+ end