sidekiq 6.0.0.pre1 → 6.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.

@@ -13,8 +13,8 @@ require_relative '../lib/sidekiq/launcher'
13
13
  include Sidekiq::Util
14
14
 
15
15
  Sidekiq.configure_server do |config|
16
- #config.options[:concurrency] = 1
17
- config.redis = { db: 13 }
16
+ config.options[:concurrency] = 10
17
+ config.redis = { db: 13, port: 6380, driver: :hiredis }
18
18
  config.options[:queues] << 'default'
19
19
  config.logger.level = Logger::ERROR
20
20
  config.average_scheduled_poll_interval = 2
@@ -28,7 +28,8 @@ class LoadWorker
28
28
  1
29
29
  end
30
30
 
31
- def perform(idx)
31
+ def perform(idx, ts=nil)
32
+ puts(Time.now.to_f - ts) if ts != nil
32
33
  #raise idx.to_s if idx % 100 == 1
33
34
  end
34
35
  end
@@ -36,14 +37,15 @@ end
36
37
  # brew tap shopify/shopify
37
38
  # brew install toxiproxy
38
39
  # gem install toxiproxy
39
- #require 'toxiproxy'
40
+ # run `toxiproxy-server` in a separate terminal window.
41
+ require 'toxiproxy'
40
42
  # simulate a non-localhost network for realer-world conditions.
41
43
  # adding 1ms of network latency has an ENORMOUS impact on benchmarks
42
- #Toxiproxy.populate([{
43
- #"name": "redis",
44
- #"listen": "127.0.0.1:6380",
45
- #"upstream": "127.0.0.1:6379"
46
- #}])
44
+ Toxiproxy.populate([{
45
+ "name": "redis",
46
+ "listen": "127.0.0.1:6380",
47
+ "upstream": "127.0.0.1:6379"
48
+ }])
47
49
 
48
50
  self_read, self_write = IO.pipe
49
51
  %w(INT TERM TSTP TTIN).each do |sig|
@@ -103,18 +105,20 @@ Sidekiq.logger.error "Created #{count*iter} jobs"
103
105
  Monitoring = Thread.new do
104
106
  watchdog("monitor thread") do
105
107
  while true
106
- sleep 1
107
- qsize, retries = Sidekiq.redis do |conn|
108
- conn.pipelined do
109
- conn.llen "queue:default"
110
- conn.zcard "retry"
111
- end
112
- end.map(&:to_i)
113
- total = qsize + retries
114
- #GC.start
108
+ sleep 0.5
109
+ qsize = Sidekiq.redis do |conn|
110
+ conn.llen "queue:default"
111
+ end
112
+ total = qsize
115
113
  Sidekiq.logger.error("RSS: #{Process.rss} Pending: #{total}")
116
114
  if total == 0
117
- Sidekiq.logger.error("Done")
115
+ Sidekiq.logger.error("Done, now here's the latency for three jobs")
116
+
117
+ LoadWorker.perform_async(1, Time.now.to_f)
118
+ LoadWorker.perform_async(2, Time.now.to_f)
119
+ LoadWorker.perform_async(3, Time.now.to_f)
120
+
121
+ sleep 0.2
118
122
  exit(0)
119
123
  end
120
124
  end
@@ -125,8 +129,8 @@ begin
125
129
  #RubyProf::exclude_threads = [ Monitoring ]
126
130
  #RubyProf.start
127
131
  fire_event(:startup)
128
- #Sidekiq.logger.error "Simulating 1ms of latency between Sidekiq and redis"
129
- #Toxiproxy[:redis].downstream(:latency, latency: 1).apply do
132
+ Sidekiq.logger.error "Simulating 1ms of latency between Sidekiq and redis"
133
+ Toxiproxy[:redis].downstream(:latency, latency: 1).apply do
130
134
  launcher = Sidekiq::Launcher.new(Sidekiq.options)
131
135
  launcher.run
132
136
 
@@ -134,7 +138,7 @@ begin
134
138
  signal = readable_io.first[0].gets.strip
135
139
  handle_signal(launcher, signal)
136
140
  end
137
- #end
141
+ end
138
142
  rescue SystemExit => e
139
143
  #Sidekiq.logger.error("Profiling...")
140
144
  #result = RubyProf.stop
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sidekiq/monitor'
4
+
5
+ if ARGV[0] == 'status'
6
+ Sidekiq::Monitor::Status.new.display(ARGV[1])
7
+ else
8
+ Sidekiq::Monitor.print_usage
9
+ end
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
  <% module_namespacing do -%>
3
- class <%= class_name %>WorkerTest < <% if defined? Minitest::Test %>Minitest::Test<% else %>MiniTest::Unit::TestCase<% end %>
3
+ class <%= class_name %>WorkerTest < Minitest::Test
4
4
  def test_example
5
5
  skip "add some examples to (or delete) #{__FILE__}"
6
6
  end
@@ -119,7 +119,7 @@ module Sidekiq
119
119
  end
120
120
  rescue Redis::CommandError => ex
121
121
  # 2850 return fake version when INFO command has (probably) been renamed
122
- raise unless ex.message =~ /unknown command/
122
+ raise unless /unknown command/.match?(ex.message)
123
123
  FAKE_INFO
124
124
  end
125
125
  end
@@ -154,7 +154,7 @@ module Sidekiq
154
154
 
155
155
  def self.default_worker_options=(hash)
156
156
  # stringify
157
- @default_worker_options = default_worker_options.merge(Hash[hash.map {|k, v| [k.to_s, v]}])
157
+ @default_worker_options = default_worker_options.merge(Hash[hash.map { |k, v| [k.to_s, v] }])
158
158
  end
159
159
 
160
160
  def self.default_worker_options
@@ -86,14 +86,14 @@ module Sidekiq
86
86
 
87
87
  pipe2_res = Sidekiq.redis { |conn|
88
88
  conn.pipelined do
89
- processes.each {|key| conn.hget(key, "busy") }
90
- queues.each {|queue| conn.llen("queue:#{queue}") }
89
+ processes.each { |key| conn.hget(key, "busy") }
90
+ queues.each { |queue| conn.llen("queue:#{queue}") }
91
91
  end
92
92
  }
93
93
 
94
94
  s = processes.size
95
95
  workers_size = pipe2_res[0...s].map(&:to_i).inject(0, &:+)
96
- enqueued = pipe2_res[s..-1].map(&:to_i).inject(0, &:+)
96
+ enqueued = pipe2_res[s..-1].map(&:to_i).inject(0, &:+)
97
97
 
98
98
  default_queue_latency = if (entry = pipe1_res[6].first)
99
99
  job = begin
@@ -122,7 +122,7 @@ module Sidekiq
122
122
  end
123
123
 
124
124
  def reset(*stats)
125
- all = %w[failed processed]
125
+ all = %w[failed processed]
126
126
  stats = stats.empty? ? all : all & stats.flatten.compact.map(&:to_s)
127
127
 
128
128
  mset_args = []
@@ -274,7 +274,7 @@ module Sidekiq
274
274
 
275
275
  loop do
276
276
  range_start = page * page_size - deleted_size
277
- range_end = range_start + page_size - 1
277
+ range_end = range_start + page_size - 1
278
278
  entries = Sidekiq.redis { |conn|
279
279
  conn.lrange @rname, range_start, range_end
280
280
  }
@@ -563,7 +563,7 @@ module Sidekiq
563
563
 
564
564
  loop do
565
565
  range_start = page * page_size + offset_size
566
- range_end = range_start + page_size - 1
566
+ range_end = range_start + page_size - 1
567
567
  elements = Sidekiq.redis { |conn|
568
568
  conn.zrange name, range_start, range_end, with_scores: true
569
569
  }
@@ -713,7 +713,7 @@ module Sidekiq
713
713
 
714
714
  ##
715
715
  # Enumerates the set of Sidekiq processes which are actively working
716
- # right now. Each process send a heartbeat to Redis every 5 seconds
716
+ # right now. Each process sends a heartbeat to Redis every 5 seconds
717
717
  # so this set should be relatively accurate, barring network partitions.
718
718
  #
719
719
  # Yields a Sidekiq::Process.
@@ -790,7 +790,7 @@ module Sidekiq
790
790
  # or Sidekiq Pro.
791
791
  def leader
792
792
  @leader ||= begin
793
- x = Sidekiq.redis {|c| c.get("dear-leader") }
793
+ x = Sidekiq.redis { |c| c.get("dear-leader") }
794
794
  # need a non-falsy value so we can memoize
795
795
  x ||= ""
796
796
  x
@@ -41,12 +41,6 @@ module Sidekiq
41
41
 
42
42
  self_read, self_write = IO.pipe
43
43
  sigs = %w[INT TERM TTIN TSTP]
44
- # USR1 and USR2 don't work on the JVM
45
- unless jruby?
46
- sigs << "USR1"
47
- sigs << "USR2"
48
- end
49
-
50
44
  sigs.each do |sig|
51
45
  trap sig do
52
46
  self_write.write("#{sig}\n")
@@ -62,8 +56,7 @@ module Sidekiq
62
56
  # touch the connection pool so it is created before we
63
57
  # fire startup and start multithreading.
64
58
  ver = Sidekiq.redis_info["redis_version"]
65
- raise "You are using Redis v#{ver}, Sidekiq requires Redis v2.8.0 or greater" if ver < "2.8"
66
- logger.warn "Sidekiq 6.0 will require Redis 4.0+, you are using Redis v#{ver}" if ver < "4"
59
+ raise "You are using Redis v#{ver}, Sidekiq requires Redis v4.0.0 or greater" if ver < "4"
67
60
 
68
61
  # Since the user can pass us a connection pool explicitly in the initializer, we
69
62
  # need to verify the size is large enough or else Sidekiq's performance is dramatically slowed.
@@ -104,9 +97,13 @@ module Sidekiq
104
97
  rescue Interrupt
105
98
  logger.info "Shutting down"
106
99
  launcher.stop
107
- # Explicitly exit so busy Processor threads can't block
108
- # process shutdown.
109
100
  logger.info "Bye!"
101
+
102
+ # Explicitly exit so busy Processor threads won't block process shutdown.
103
+ #
104
+ # NB: slow at_exit handlers will prevent a timely exit if they take
105
+ # a while to run. If Sidekiq is getting here but the process isn't exiting,
106
+ # use the TTIN signal to determine where things are stuck.
110
107
  exit(0)
111
108
  end
112
109
  end
@@ -150,17 +147,13 @@ module Sidekiq
150
147
  # TERM is the signal that Sidekiq must exit.
151
148
  # Heroku sends TERM and then waits 30 seconds for process to exit.
152
149
  "TERM" => ->(cli) { raise Interrupt },
153
- "USR1" => ->(cli) {
154
- Sidekiq.logger.info "Received USR1, no longer accepting new work"
155
- cli.launcher.quiet
156
- },
157
150
  "TSTP" => ->(cli) {
158
151
  Sidekiq.logger.info "Received TSTP, no longer accepting new work"
159
152
  cli.launcher.quiet
160
153
  },
161
154
  "TTIN" => ->(cli) {
162
155
  Thread.list.each do |thread|
163
- Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread["sidekiq_label"]}"
156
+ Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread.name}"
164
157
  if thread.backtrace
165
158
  Sidekiq.logger.warn thread.backtrace.join("\n")
166
159
  else
@@ -276,7 +269,7 @@ module Sidekiq
276
269
  if !File.exist?(options[:require]) ||
277
270
  (File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
278
271
  logger.info "=================================================================="
279
- logger.info " Please point sidekiq to a Rails 4/5 application or a Ruby file "
272
+ logger.info " Please point Sidekiq to a Rails application or a Ruby file "
280
273
  logger.info " to load your worker classes with -r [DIR|FILE]."
281
274
  logger.info "=================================================================="
282
275
  logger.info @parser
@@ -297,7 +290,7 @@ module Sidekiq
297
290
  end
298
291
 
299
292
  o.on "-d", "--daemon", "Daemonize process" do |arg|
300
- puts "WARNING: Daemonization mode was removed in Sidekiq 6.0, please use a proper process supervisor to start and manage your services"
293
+ puts "ERROR: Daemonization mode was removed in Sidekiq 6.0, please use a proper process supervisor to start and manage your services"
301
294
  end
302
295
 
303
296
  o.on "-e", "--environment ENV", "Application environment" do |arg|
@@ -330,11 +323,11 @@ module Sidekiq
330
323
  end
331
324
 
332
325
  o.on "-L", "--logfile PATH", "path to writable logfile" do |arg|
333
- puts "WARNING: Logfile redirection was removed in Sidekiq 6.0, Sidekiq will only log to STDOUT"
326
+ puts "ERROR: Logfile redirection was removed in Sidekiq 6.0, Sidekiq will only log to STDOUT"
334
327
  end
335
328
 
336
329
  o.on "-P", "--pidfile PATH", "path to pidfile" do |arg|
337
- puts "WARNING: PID file creation was removed in Sidekiq 6.0, please use a proper process supervisor to start and manage your services"
330
+ puts "ERROR: PID file creation was removed in Sidekiq 6.0, please use a proper process supervisor to start and manage your services"
338
331
  end
339
332
 
340
333
  o.on "-V", "--version", "Print version and exit" do |arg|
@@ -370,12 +363,6 @@ module Sidekiq
370
363
  opts = opts.merge(opts.delete(environment.to_sym) || {})
371
364
  parse_queues(opts, opts.delete(:queues) || [])
372
365
 
373
- ns = opts.delete(:namespace)
374
- if ns
375
- # logger hasn't been initialized yet, puts is all we have.
376
- puts("namespace should be set in your ruby initializer, is ignored in config file")
377
- puts("config.redis = { :url => ..., :namespace => '#{ns}' }")
378
- end
379
366
  opts
380
367
  end
381
368
 
@@ -194,14 +194,14 @@ module Sidekiq
194
194
  [at, Sidekiq.dump_json(hash)]
195
195
  })
196
196
  else
197
- q = payloads.first["queue"]
197
+ queue = payloads.first["queue"]
198
198
  now = Time.now.to_f
199
199
  to_push = payloads.map { |entry|
200
200
  entry["enqueued_at"] = now
201
201
  Sidekiq.dump_json(entry)
202
202
  }
203
- conn.sadd("queues", q)
204
- conn.lpush("queue:#{q}", to_push)
203
+ conn.sadd("queues", queue)
204
+ conn.lpush("queue:#{queue}", to_push)
205
205
  end
206
206
  end
207
207
 
@@ -5,10 +5,7 @@ require "sidekiq/fetch"
5
5
  require "sidekiq/scheduled"
6
6
 
7
7
  module Sidekiq
8
- # The Launcher is a very simple Actor whose job is to
9
- # start, monitor and stop the core Actors in Sidekiq.
10
- # If any of these actors die, the Sidekiq process exits
11
- # immediately.
8
+ # The Launcher starts the Manager and Poller threads and provides the process heartbeat.
12
9
  class Launcher
13
10
  include Util
14
11
 
@@ -148,7 +145,7 @@ module Sidekiq
148
145
 
149
146
  return unless msg
150
147
 
151
- ::Process.kill(msg, $PID)
148
+ ::Process.kill(msg, ::Process.pid)
152
149
  rescue => e
153
150
  # ignore all redis/network issues
154
151
  logger.error("heartbeat: #{e.message}")
@@ -163,7 +160,7 @@ module Sidekiq
163
160
  {
164
161
  "hostname" => hostname,
165
162
  "started_at" => Time.now.to_f,
166
- "pid" => $PID,
163
+ "pid" => ::Process.pid,
167
164
  "tag" => @options[:tag] || "",
168
165
  "concurrency" => @options[:concurrency],
169
166
  "queues" => @options[:queues].uniq,
@@ -112,7 +112,7 @@ module Sidekiq
112
112
  end
113
113
 
114
114
  if cleanup.size > 0
115
- jobs = cleanup.map {|p| p.job }.compact
115
+ jobs = cleanup.map { |p| p.job }.compact
116
116
 
117
117
  logger.warn { "Terminating #{cleanup.size} busy worker threads" }
118
118
  logger.warn { "Work still in progress #{jobs.inspect}" }
@@ -140,7 +140,7 @@ module Sidekiq
140
140
 
141
141
  def initialize(klass, *args)
142
142
  @klass = klass
143
- @args = args
143
+ @args = args
144
144
  end
145
145
 
146
146
  def make_new
@@ -3,19 +3,20 @@
3
3
  require "fileutils"
4
4
  require "sidekiq/api"
5
5
 
6
- class Sidekiq::Ctl
6
+ class Sidekiq::Monitor
7
7
  CMD = File.basename($PROGRAM_NAME)
8
8
 
9
9
  attr_reader :stage
10
10
 
11
11
  def self.print_usage
12
- puts "#{CMD} - control Sidekiq from the command line."
12
+ puts "#{CMD} - monitor Sidekiq from the command line."
13
13
  puts
14
14
  puts "Usage: #{CMD} status <section>"
15
15
  puts
16
16
  puts " <section> (optional) view a specific section of the status output"
17
- puts " Valid sections are: #{Sidekiq::Ctl::Status::VALID_SECTIONS.join(", ")}"
17
+ puts " Valid sections are: #{Sidekiq::Monitor::Status::VALID_SECTIONS.join(", ")}"
18
18
  puts
19
+ puts "Set REDIS_URL to the location of your Redis server if not monitoring localhost."
19
20
  end
20
21
 
21
22
  class Status
@@ -4,6 +4,7 @@ require "sidekiq/util"
4
4
  require "sidekiq/fetch"
5
5
  require "sidekiq/job_logger"
6
6
  require "sidekiq/job_retry"
7
+
7
8
  module Sidekiq
8
9
  ##
9
10
  # The Processor is a standalone thread which:
@@ -125,7 +126,7 @@ module Sidekiq
125
126
  # the Reloader. It handles code loading, db connection management, etc.
126
127
  # Effectively this block denotes a "unit of work" to Rails.
127
128
  @reloader.call do
128
- klass = constantize(job_hash["class"])
129
+ klass = constantize(job_hash["class"])
129
130
  worker = klass.new
130
131
  worker.jid = job_hash["jid"]
131
132
  @retrier.local(worker, pristine, queue) do
@@ -153,21 +154,22 @@ module Sidekiq
153
154
  return work.acknowledge
154
155
  end
155
156
 
156
- ack = true
157
+ ack = false
157
158
  begin
158
159
  dispatch(job_hash, queue) do |worker|
159
160
  Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
160
161
  execute_job(worker, cloned(job_hash["args"]))
161
162
  end
162
163
  end
164
+ ack = true
163
165
  rescue Sidekiq::Shutdown
164
166
  # Had to force kill this job because it didn't finish
165
167
  # within the timeout. Don't acknowledge the work since
166
168
  # we didn't properly finish it.
167
- ack = false
168
169
  rescue Sidekiq::JobRetry::Handled => h
169
170
  # this is the common case: job raised error and Sidekiq::JobRetry::Handled
170
171
  # signals that we created a retry successfully. We can acknowlege the job.
172
+ ack = true
171
173
  e = h.cause || h
172
174
  handle_exception(e, {context: "Job raised exception", job: job_hash, jobstr: jobstr})
173
175
  raise e
@@ -175,11 +177,15 @@ module Sidekiq
175
177
  # Unexpected error! This is very bad and indicates an exception that got past
176
178
  # the retry subsystem (e.g. network partition). We won't acknowledge the job
177
179
  # so it can be rescued when using Sidekiq Pro.
178
- ack = false
179
180
  handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
180
181
  raise e
181
182
  ensure
182
- work.acknowledge if ack
183
+ if ack
184
+ # We don't want a shutdown signal to interrupt job acknowledgment.
185
+ Thread.handle_interrupt(Sidekiq::Shutdown => :never) do
186
+ work.acknowledge
187
+ end
188
+ end
183
189
  end
184
190
  end
185
191
 
@@ -1,15 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "sidekiq/worker"
4
+
3
5
  module Sidekiq
4
6
  class Rails < ::Rails::Engine
7
+ # By including the Options module, we allow AJs to directly control sidekiq features
8
+ # via the *sidekiq_options* class method and, for instance, not use AJ's retry system.
9
+ # AJ retries don't show up in the Sidekiq UI Retries tab, save any error data, can't be
10
+ # manually retried, don't automatically die, etc.
11
+ #
12
+ # class SomeJob < ActiveJob::Base
13
+ # queue_as :default
14
+ # sidekiq_options retry: 3, backtrace: 10
15
+ # def perform
16
+ # end
17
+ # end
18
+ initializer "sidekiq.active_job_integration" do
19
+ ActiveSupport.on_load(:active_job) do
20
+ include ::Sidekiq::Worker::Options unless respond_to?(:sidekiq_options)
21
+ end
22
+ end
23
+
24
+ # This hook happens after all initializers are run, just before returning
25
+ # from config/environment.rb back to sidekiq/cli.rb.
26
+ # We have to add the reloader after initialize to see if cache_classes has
27
+ # been turned on.
28
+ #
29
+ # None of this matters on the client-side, only within the Sidekiq process itself.
5
30
  config.after_initialize do
6
- # This hook happens after all initializers are run, just before returning
7
- # from config/environment.rb back to sidekiq/cli.rb.
8
- # We have to add the reloader after initialize to see if cache_classes has
9
- # been turned on.
10
- #
11
- # None of this matters on the client-side, only within the Sidekiq process itself.
12
- #
13
31
  Sidekiq.configure_server do |_|
14
32
  Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
15
33
  end