sidekiq 6.0.7 → 6.4.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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +189 -2
  3. data/LICENSE +3 -3
  4. data/README.md +11 -10
  5. data/bin/sidekiq +8 -3
  6. data/bin/sidekiqload +57 -65
  7. data/bin/sidekiqmon +1 -1
  8. data/lib/generators/sidekiq/job_generator.rb +57 -0
  9. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  10. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  11. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  12. data/lib/sidekiq/api.rb +164 -116
  13. data/lib/sidekiq/cli.rb +49 -15
  14. data/lib/sidekiq/client.rb +51 -70
  15. data/lib/sidekiq/delay.rb +2 -0
  16. data/lib/sidekiq/extensions/action_mailer.rb +3 -2
  17. data/lib/sidekiq/extensions/active_record.rb +4 -3
  18. data/lib/sidekiq/extensions/class_methods.rb +5 -4
  19. data/lib/sidekiq/extensions/generic_proxy.rb +4 -2
  20. data/lib/sidekiq/fetch.rb +32 -23
  21. data/lib/sidekiq/job.rb +13 -0
  22. data/lib/sidekiq/job_logger.rb +16 -28
  23. data/lib/sidekiq/job_retry.rb +32 -33
  24. data/lib/sidekiq/job_util.rb +67 -0
  25. data/lib/sidekiq/launcher.rb +113 -54
  26. data/lib/sidekiq/logger.rb +11 -20
  27. data/lib/sidekiq/manager.rb +16 -18
  28. data/lib/sidekiq/middleware/chain.rb +10 -8
  29. data/lib/sidekiq/middleware/current_attributes.rb +57 -0
  30. data/lib/sidekiq/middleware/i18n.rb +4 -4
  31. data/lib/sidekiq/monitor.rb +1 -1
  32. data/lib/sidekiq/paginator.rb +8 -8
  33. data/lib/sidekiq/processor.rb +31 -31
  34. data/lib/sidekiq/rails.rb +36 -20
  35. data/lib/sidekiq/redis_connection.rb +16 -15
  36. data/lib/sidekiq/scheduled.rb +51 -16
  37. data/lib/sidekiq/sd_notify.rb +1 -1
  38. data/lib/sidekiq/testing/inline.rb +4 -4
  39. data/lib/sidekiq/testing.rb +38 -39
  40. data/lib/sidekiq/util.rb +41 -0
  41. data/lib/sidekiq/version.rb +1 -1
  42. data/lib/sidekiq/web/action.rb +2 -2
  43. data/lib/sidekiq/web/application.rb +21 -12
  44. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  45. data/lib/sidekiq/web/helpers.rb +39 -33
  46. data/lib/sidekiq/web/router.rb +5 -2
  47. data/lib/sidekiq/web.rb +36 -72
  48. data/lib/sidekiq/worker.rb +135 -16
  49. data/lib/sidekiq.rb +33 -17
  50. data/sidekiq.gemspec +11 -4
  51. data/web/assets/images/apple-touch-icon.png +0 -0
  52. data/web/assets/javascripts/application.js +113 -65
  53. data/web/assets/javascripts/dashboard.js +51 -51
  54. data/web/assets/stylesheets/application-dark.css +64 -43
  55. data/web/assets/stylesheets/application-rtl.css +0 -4
  56. data/web/assets/stylesheets/application.css +42 -239
  57. data/web/locales/ar.yml +8 -2
  58. data/web/locales/en.yml +4 -1
  59. data/web/locales/es.yml +18 -2
  60. data/web/locales/fr.yml +8 -1
  61. data/web/locales/ja.yml +3 -0
  62. data/web/locales/lt.yml +1 -1
  63. data/web/locales/pl.yml +4 -4
  64. data/web/locales/ru.yml +4 -0
  65. data/web/views/_footer.erb +1 -1
  66. data/web/views/_job_info.erb +1 -1
  67. data/web/views/_poll_link.erb +2 -5
  68. data/web/views/_summary.erb +7 -7
  69. data/web/views/busy.erb +51 -20
  70. data/web/views/dashboard.erb +22 -14
  71. data/web/views/dead.erb +1 -1
  72. data/web/views/layout.erb +2 -1
  73. data/web/views/morgue.erb +6 -6
  74. data/web/views/queue.erb +11 -11
  75. data/web/views/queues.erb +4 -4
  76. data/web/views/retries.erb +7 -7
  77. data/web/views/retry.erb +1 -1
  78. data/web/views/scheduled.erb +1 -1
  79. metadata +24 -49
  80. data/.circleci/config.yml +0 -60
  81. data/.github/contributing.md +0 -32
  82. data/.github/issue_template.md +0 -11
  83. data/.gitignore +0 -13
  84. data/.standard.yml +0 -20
  85. data/3.0-Upgrade.md +0 -70
  86. data/4.0-Upgrade.md +0 -53
  87. data/5.0-Upgrade.md +0 -56
  88. data/6.0-Upgrade.md +0 -72
  89. data/COMM-LICENSE +0 -97
  90. data/Ent-2.0-Upgrade.md +0 -37
  91. data/Ent-Changes.md +0 -256
  92. data/Gemfile +0 -24
  93. data/Gemfile.lock +0 -208
  94. data/Pro-2.0-Upgrade.md +0 -138
  95. data/Pro-3.0-Upgrade.md +0 -44
  96. data/Pro-4.0-Upgrade.md +0 -35
  97. data/Pro-5.0-Upgrade.md +0 -25
  98. data/Pro-Changes.md +0 -782
  99. data/Rakefile +0 -10
  100. data/code_of_conduct.md +0 -50
  101. data/lib/generators/sidekiq/worker_generator.rb +0 -57
data/lib/sidekiq/api.rb CHANGED
@@ -8,7 +8,7 @@ require "base64"
8
8
  module Sidekiq
9
9
  class Stats
10
10
  def initialize
11
- fetch_stats!
11
+ fetch_stats_fast!
12
12
  end
13
13
 
14
14
  def processed
@@ -51,50 +51,33 @@ module Sidekiq
51
51
  Sidekiq::Stats::Queues.new.lengths
52
52
  end
53
53
 
54
- def fetch_stats!
54
+ # O(1) redis calls
55
+ def fetch_stats_fast!
55
56
  pipe1_res = Sidekiq.redis { |conn|
56
- conn.pipelined do
57
- conn.get("stat:processed")
58
- conn.get("stat:failed")
59
- conn.zcard("schedule")
60
- conn.zcard("retry")
61
- conn.zcard("dead")
62
- conn.scard("processes")
63
- conn.lrange("queue:default", -1, -1)
57
+ conn.pipelined do |pipeline|
58
+ pipeline.get("stat:processed")
59
+ pipeline.get("stat:failed")
60
+ pipeline.zcard("schedule")
61
+ pipeline.zcard("retry")
62
+ pipeline.zcard("dead")
63
+ pipeline.scard("processes")
64
+ pipeline.lrange("queue:default", -1, -1)
64
65
  end
65
66
  }
66
67
 
67
- processes = Sidekiq.redis { |conn|
68
- conn.sscan_each("processes").to_a
69
- }
70
-
71
- queues = Sidekiq.redis { |conn|
72
- conn.sscan_each("queues").to_a
73
- }
74
-
75
- pipe2_res = Sidekiq.redis { |conn|
76
- conn.pipelined do
77
- processes.each { |key| conn.hget(key, "busy") }
78
- queues.each { |queue| conn.llen("queue:#{queue}") }
79
- end
80
- }
81
-
82
- s = processes.size
83
- workers_size = pipe2_res[0...s].sum(&:to_i)
84
- enqueued = pipe2_res[s..-1].sum(&:to_i)
85
-
86
68
  default_queue_latency = if (entry = pipe1_res[6].first)
87
69
  job = begin
88
- Sidekiq.load_json(entry)
89
- rescue
90
- {}
91
- end
70
+ Sidekiq.load_json(entry)
71
+ rescue
72
+ {}
73
+ end
92
74
  now = Time.now.to_f
93
75
  thence = job["enqueued_at"] || now
94
76
  now - thence
95
77
  else
96
78
  0
97
79
  end
80
+
98
81
  @stats = {
99
82
  processed: pipe1_res[0].to_i,
100
83
  failed: pipe1_res[1].to_i,
@@ -103,10 +86,39 @@ module Sidekiq
103
86
  dead_size: pipe1_res[4],
104
87
  processes_size: pipe1_res[5],
105
88
 
106
- default_queue_latency: default_queue_latency,
107
- workers_size: workers_size,
108
- enqueued: enqueued
89
+ default_queue_latency: default_queue_latency
90
+ }
91
+ end
92
+
93
+ # O(number of processes + number of queues) redis calls
94
+ def fetch_stats_slow!
95
+ processes = Sidekiq.redis { |conn|
96
+ conn.sscan_each("processes").to_a
97
+ }
98
+
99
+ queues = Sidekiq.redis { |conn|
100
+ conn.sscan_each("queues").to_a
101
+ }
102
+
103
+ pipe2_res = Sidekiq.redis { |conn|
104
+ conn.pipelined do |pipeline|
105
+ processes.each { |key| pipeline.hget(key, "busy") }
106
+ queues.each { |queue| pipeline.llen("queue:#{queue}") }
107
+ end
109
108
  }
109
+
110
+ s = processes.size
111
+ workers_size = pipe2_res[0...s].sum(&:to_i)
112
+ enqueued = pipe2_res[s..-1].sum(&:to_i)
113
+
114
+ @stats[:workers_size] = workers_size
115
+ @stats[:enqueued] = enqueued
116
+ @stats
117
+ end
118
+
119
+ def fetch_stats!
120
+ fetch_stats_fast!
121
+ fetch_stats_slow!
110
122
  end
111
123
 
112
124
  def reset(*stats)
@@ -126,7 +138,8 @@ module Sidekiq
126
138
  private
127
139
 
128
140
  def stat(s)
129
- @stats[s]
141
+ fetch_stats_slow! if @stats[s].nil?
142
+ @stats[s] || raise(ArgumentError, "Unknown stat #{s}")
130
143
  end
131
144
 
132
145
  class Queues
@@ -134,20 +147,22 @@ module Sidekiq
134
147
  Sidekiq.redis do |conn|
135
148
  queues = conn.sscan_each("queues").to_a
136
149
 
137
- lengths = conn.pipelined {
150
+ lengths = conn.pipelined { |pipeline|
138
151
  queues.each do |queue|
139
- conn.llen("queue:#{queue}")
152
+ pipeline.llen("queue:#{queue}")
140
153
  end
141
154
  }
142
155
 
143
156
  array_of_arrays = queues.zip(lengths).sort_by { |_, size| -size }
144
- Hash[array_of_arrays]
157
+ array_of_arrays.to_h
145
158
  end
146
159
  end
147
160
  end
148
161
 
149
162
  class History
150
163
  def initialize(days_previous, start_date = nil)
164
+ # we only store five years of data in Redis
165
+ raise ArgumentError if days_previous < 1 || days_previous > (5 * 365)
151
166
  @days_previous = days_previous
152
167
  @start_date = start_date || Time.now.utc.to_date
153
168
  end
@@ -255,7 +270,7 @@ module Sidekiq
255
270
  break if entries.empty?
256
271
  page += 1
257
272
  entries.each do |entry|
258
- yield Job.new(entry, @name)
273
+ yield JobRecord.new(entry, @name)
259
274
  end
260
275
  deleted_size = initial_size - size
261
276
  end
@@ -265,16 +280,16 @@ module Sidekiq
265
280
  # Find the job with the given JID within this queue.
266
281
  #
267
282
  # This is a slow, inefficient operation. Do not use under
268
- # normal conditions. Sidekiq Pro contains a faster version.
283
+ # normal conditions.
269
284
  def find_job(jid)
270
285
  detect { |j| j.jid == jid }
271
286
  end
272
287
 
273
288
  def clear
274
289
  Sidekiq.redis do |conn|
275
- conn.multi do
276
- conn.unlink(@rname)
277
- conn.srem("queues", name)
290
+ conn.multi do |transaction|
291
+ transaction.unlink(@rname)
292
+ transaction.srem("queues", name)
278
293
  end
279
294
  end
280
295
  end
@@ -286,9 +301,9 @@ module Sidekiq
286
301
  # sorted set.
287
302
  #
288
303
  # The job should be considered immutable but may be
289
- # removed from the queue via Job#delete.
304
+ # removed from the queue via JobRecord#delete.
290
305
  #
291
- class Job
306
+ class JobRecord
292
307
  attr_reader :item
293
308
  attr_reader :value
294
309
 
@@ -316,48 +331,54 @@ module Sidekiq
316
331
 
317
332
  def display_class
318
333
  # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
319
- @klass ||= case klass
320
- when /\ASidekiq::Extensions::Delayed/
321
- safe_load(args[0], klass) do |target, method, _|
322
- "#{target}.#{method}"
323
- end
324
- when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
325
- job_class = @item["wrapped"] || args[0]
326
- if job_class == "ActionMailer::DeliveryJob" || job_class == "ActionMailer::MailDeliveryJob"
327
- # MailerClass#mailer_method
328
- args[0]["arguments"][0..1].join("#")
329
- else
330
- job_class
331
- end
332
- else
333
- klass
334
+ @klass ||= self["display_class"] || begin
335
+ case klass
336
+ when /\ASidekiq::Extensions::Delayed/
337
+ safe_load(args[0], klass) do |target, method, _|
338
+ "#{target}.#{method}"
339
+ end
340
+ when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
341
+ job_class = @item["wrapped"] || args[0]
342
+ if job_class == "ActionMailer::DeliveryJob" || job_class == "ActionMailer::MailDeliveryJob"
343
+ # MailerClass#mailer_method
344
+ args[0]["arguments"][0..1].join("#")
345
+ else
346
+ job_class
347
+ end
348
+ else
349
+ klass
350
+ end
334
351
  end
335
352
  end
336
353
 
337
354
  def display_args
338
355
  # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
339
356
  @display_args ||= case klass
340
- when /\ASidekiq::Extensions::Delayed/
341
- safe_load(args[0], args) do |_, _, arg|
342
- arg
343
- end
344
- when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
345
- job_args = self["wrapped"] ? args[0]["arguments"] : []
346
- if (self["wrapped"] || args[0]) == "ActionMailer::DeliveryJob"
347
- # remove MailerClass, mailer_method and 'deliver_now'
348
- job_args.drop(3)
349
- elsif (self["wrapped"] || args[0]) == "ActionMailer::MailDeliveryJob"
350
- # remove MailerClass, mailer_method and 'deliver_now'
351
- job_args.drop(3).first["args"]
352
- else
353
- job_args
354
- end
355
- else
356
- if self["encrypt"]
357
- # no point in showing 150+ bytes of random garbage
358
- args[-1] = "[encrypted data]"
359
- end
360
- args
357
+ when /\ASidekiq::Extensions::Delayed/
358
+ safe_load(args[0], args) do |_, _, arg, kwarg|
359
+ if !kwarg || kwarg.empty?
360
+ arg
361
+ else
362
+ [arg, kwarg]
363
+ end
364
+ end
365
+ when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
366
+ job_args = self["wrapped"] ? args[0]["arguments"] : []
367
+ if (self["wrapped"] || args[0]) == "ActionMailer::DeliveryJob"
368
+ # remove MailerClass, mailer_method and 'deliver_now'
369
+ job_args.drop(3)
370
+ elsif (self["wrapped"] || args[0]) == "ActionMailer::MailDeliveryJob"
371
+ # remove MailerClass, mailer_method and 'deliver_now'
372
+ job_args.drop(3).first["args"]
373
+ else
374
+ job_args
375
+ end
376
+ else
377
+ if self["encrypt"]
378
+ # no point in showing 150+ bytes of random garbage
379
+ args[-1] = "[encrypted data]"
380
+ end
381
+ args
361
382
  end
362
383
  end
363
384
 
@@ -443,7 +464,7 @@ module Sidekiq
443
464
  end
444
465
  end
445
466
 
446
- class SortedEntry < Job
467
+ class SortedEntry < JobRecord
447
468
  attr_reader :score
448
469
  attr_reader :parent
449
470
 
@@ -502,9 +523,9 @@ module Sidekiq
502
523
 
503
524
  def remove_job
504
525
  Sidekiq.redis do |conn|
505
- results = conn.multi {
506
- conn.zrangebyscore(parent.name, score, score)
507
- conn.zremrangebyscore(parent.name, score, score)
526
+ results = conn.multi { |transaction|
527
+ transaction.zrangebyscore(parent.name, score, score)
528
+ transaction.zremrangebyscore(parent.name, score, score)
508
529
  }.first
509
530
 
510
531
  if results.size == 1
@@ -525,9 +546,9 @@ module Sidekiq
525
546
  yield msg if msg
526
547
 
527
548
  # push the rest back onto the sorted set
528
- conn.multi do
549
+ conn.multi do |transaction|
529
550
  nonmatched.each do |message|
530
- conn.zadd(parent.name, score.to_f.to_s, message)
551
+ transaction.zadd(parent.name, score.to_f.to_s, message)
531
552
  end
532
553
  end
533
554
  end
@@ -714,10 +735,10 @@ module Sidekiq
714
735
  def kill(message, opts = {})
715
736
  now = Time.now.to_f
716
737
  Sidekiq.redis do |conn|
717
- conn.multi do
718
- conn.zadd(name, now.to_s, message)
719
- conn.zremrangebyscore(name, "-inf", now - self.class.timeout)
720
- conn.zremrangebyrank(name, 0, - self.class.max_jobs)
738
+ conn.multi do |transaction|
739
+ transaction.zadd(name, now.to_s, message)
740
+ transaction.zremrangebyscore(name, "-inf", now - self.class.timeout)
741
+ transaction.zremrangebyrank(name, 0, - self.class.max_jobs)
721
742
  end
722
743
  end
723
744
 
@@ -765,9 +786,9 @@ module Sidekiq
765
786
  count = 0
766
787
  Sidekiq.redis do |conn|
767
788
  procs = conn.sscan_each("processes").to_a.sort
768
- heartbeats = conn.pipelined {
789
+ heartbeats = conn.pipelined { |pipeline|
769
790
  procs.each do |key|
770
- conn.hget(key, "info")
791
+ pipeline.hget(key, "info")
771
792
  end
772
793
  }
773
794
 
@@ -789,21 +810,25 @@ module Sidekiq
789
810
  # We're making a tradeoff here between consuming more memory instead of
790
811
  # making more roundtrips to Redis, but if you have hundreds or thousands of workers,
791
812
  # you'll be happier this way
792
- conn.pipelined do
813
+ conn.pipelined do |pipeline|
793
814
  procs.each do |key|
794
- conn.hmget(key, "info", "busy", "beat", "quiet")
815
+ pipeline.hmget(key, "info", "busy", "beat", "quiet", "rss", "rtt_us")
795
816
  end
796
817
  end
797
818
  }
798
819
 
799
- result.each do |info, busy, at_s, quiet|
820
+ result.each do |info, busy, at_s, quiet, rss, rtt|
800
821
  # If a process is stopped between when we query Redis for `procs` and
801
822
  # when we query for `result`, we will have an item in `result` that is
802
823
  # composed of `nil` values.
803
824
  next if info.nil?
804
825
 
805
826
  hash = Sidekiq.load_json(info)
806
- yield Process.new(hash.merge("busy" => busy.to_i, "beat" => at_s.to_f, "quiet" => quiet))
827
+ yield Process.new(hash.merge("busy" => busy.to_i,
828
+ "beat" => at_s.to_f,
829
+ "quiet" => quiet,
830
+ "rss" => rss.to_i,
831
+ "rtt_us" => rtt.to_i))
807
832
  end
808
833
  end
809
834
 
@@ -815,6 +840,18 @@ module Sidekiq
815
840
  Sidekiq.redis { |conn| conn.scard("processes") }
816
841
  end
817
842
 
843
+ # Total number of threads available to execute jobs.
844
+ # For Sidekiq Enterprise customers this number (in production) must be
845
+ # less than or equal to your licensed concurrency.
846
+ def total_concurrency
847
+ sum { |x| x["concurrency"].to_i }
848
+ end
849
+
850
+ def total_rss_in_kb
851
+ sum { |x| x["rss"].to_i }
852
+ end
853
+ alias_method :total_rss, :total_rss_in_kb
854
+
818
855
  # Returns the identity of the current cluster leader or "" if no leader.
819
856
  # This is a Sidekiq Enterprise feature, will always return "" in Sidekiq
820
857
  # or Sidekiq Pro.
@@ -864,6 +901,10 @@ module Sidekiq
864
901
  self["identity"]
865
902
  end
866
903
 
904
+ def queues
905
+ self["queues"]
906
+ end
907
+
867
908
  def quiet!
868
909
  signal("TSTP")
869
910
  end
@@ -885,17 +926,17 @@ module Sidekiq
885
926
  def signal(sig)
886
927
  key = "#{identity}-signals"
887
928
  Sidekiq.redis do |c|
888
- c.multi do
889
- c.lpush(key, sig)
890
- c.expire(key, 60)
929
+ c.multi do |transaction|
930
+ transaction.lpush(key, sig)
931
+ transaction.expire(key, 60)
891
932
  end
892
933
  end
893
934
  end
894
935
  end
895
936
 
896
937
  ##
897
- # A worker is a thread that is currently processing a job.
898
- # Programmatic access to the current active worker set.
938
+ # The WorkSet stores the work being done by this Sidekiq cluster.
939
+ # It tracks the process and thread working on each job.
899
940
  #
900
941
  # WARNING WARNING WARNING
901
942
  #
@@ -903,26 +944,27 @@ module Sidekiq
903
944
  # If you call #size => 5 and then expect #each to be
904
945
  # called 5 times, you're going to have a bad time.
905
946
  #
906
- # workers = Sidekiq::Workers.new
907
- # workers.size => 2
908
- # workers.each do |process_id, thread_id, work|
947
+ # works = Sidekiq::WorkSet.new
948
+ # works.size => 2
949
+ # works.each do |process_id, thread_id, work|
909
950
  # # process_id is a unique identifier per Sidekiq process
910
951
  # # thread_id is a unique identifier per thread
911
952
  # # work is a Hash which looks like:
912
- # # { 'queue' => name, 'run_at' => timestamp, 'payload' => msg }
953
+ # # { 'queue' => name, 'run_at' => timestamp, 'payload' => job_hash }
913
954
  # # run_at is an epoch Integer.
914
955
  # end
915
956
  #
916
- class Workers
957
+ class WorkSet
917
958
  include Enumerable
918
959
 
919
- def each
960
+ def each(&block)
961
+ results = []
920
962
  Sidekiq.redis do |conn|
921
963
  procs = conn.sscan_each("processes").to_a
922
964
  procs.sort.each do |key|
923
- valid, workers = conn.pipelined {
924
- conn.exists(key)
925
- conn.hgetall("#{key}:workers")
965
+ valid, workers = conn.pipelined { |pipeline|
966
+ pipeline.exists?(key)
967
+ pipeline.hgetall("#{key}:work")
926
968
  }
927
969
  next unless valid
928
970
  workers.each_pair do |tid, json|
@@ -930,10 +972,12 @@ module Sidekiq
930
972
  p = hsh["payload"]
931
973
  # avoid breaking API, this is a side effect of the JSON optimization in #4316
932
974
  hsh["payload"] = Sidekiq.load_json(p) if p.is_a?(String)
933
- yield key, tid, hsh
975
+ results << [key, tid, hsh]
934
976
  end
935
977
  end
936
978
  end
979
+
980
+ results.sort_by { |(_, _, hsh)| hsh["run_at"] }.each(&block)
937
981
  end
938
982
 
939
983
  # Note that #size is only as accurate as Sidekiq's heartbeat,
@@ -948,13 +992,17 @@ module Sidekiq
948
992
  if procs.empty?
949
993
  0
950
994
  else
951
- conn.pipelined {
995
+ conn.pipelined { |pipeline|
952
996
  procs.each do |key|
953
- conn.hget(key, "busy")
997
+ pipeline.hget(key, "busy")
954
998
  end
955
999
  }.sum(&:to_i)
956
1000
  end
957
1001
  end
958
1002
  end
959
1003
  end
1004
+ # Since "worker" is a nebulous term, we've deprecated the use of this class name.
1005
+ # Is "worker" a process, a type of job, a thread? Undefined!
1006
+ # WorkSet better describes the data.
1007
+ Workers = WorkSet
960
1008
  end
data/lib/sidekiq/cli.rb CHANGED
@@ -20,7 +20,7 @@ module Sidekiq
20
20
  attr_accessor :launcher
21
21
  attr_accessor :environment
22
22
 
23
- def parse(args = ARGV)
23
+ def parse(args = ARGV.dup)
24
24
  setup_options(args)
25
25
  initialize_logger
26
26
  validate!
@@ -33,8 +33,9 @@ module Sidekiq
33
33
  # Code within this method is not tested because it alters
34
34
  # global process state irreversibly. PRs which improve the
35
35
  # test coverage of Sidekiq::CLI are welcomed.
36
- def run
37
- boot_system
36
+ def run(boot_app: true)
37
+ boot_application if boot_app
38
+
38
39
  if environment == "development" && $stdout.tty? && Sidekiq.log_formatter.is_a?(Sidekiq::Logger::Formatters::Pretty)
39
40
  print_banner
40
41
  end
@@ -43,9 +44,17 @@ module Sidekiq
43
44
  self_read, self_write = IO.pipe
44
45
  sigs = %w[INT TERM TTIN TSTP]
45
46
  # USR1 and USR2 don't work on the JVM
46
- sigs << "USR2" unless jruby?
47
+ sigs << "USR2" if Sidekiq.pro? && !jruby?
47
48
  sigs.each do |sig|
48
- trap sig do
49
+ old_handler = Signal.trap(sig) do
50
+ if old_handler.respond_to?(:call)
51
+ begin
52
+ old_handler.call
53
+ rescue Exception => exc
54
+ # signal handlers can't use Logger so puts only
55
+ puts ["Error in #{sig} handler", exc].inspect
56
+ end
57
+ end
49
58
  self_write.puts(sig)
50
59
  end
51
60
  rescue ArgumentError
@@ -58,9 +67,22 @@ module Sidekiq
58
67
 
59
68
  # touch the connection pool so it is created before we
60
69
  # fire startup and start multithreading.
61
- ver = Sidekiq.redis_info["redis_version"]
70
+ info = Sidekiq.redis_info
71
+ ver = info["redis_version"]
62
72
  raise "You are connecting to Redis v#{ver}, Sidekiq requires Redis v4.0.0 or greater" if ver < "4"
63
73
 
74
+ maxmemory_policy = info["maxmemory_policy"]
75
+ if maxmemory_policy != "noeviction"
76
+ logger.warn <<~EOM
77
+
78
+
79
+ WARNING: Your Redis instance will evict Sidekiq data under heavy load.
80
+ The 'noeviction' maxmemory policy is recommended (current policy: '#{maxmemory_policy}').
81
+ See: https://github.com/mperham/sidekiq/wiki/Using-Redis#memory
82
+
83
+ EOM
84
+ end
85
+
64
86
  # Since the user can pass us a connection pool explicitly in the initializer, we
65
87
  # need to verify the size is large enough or else Sidekiq's performance is dramatically slowed.
66
88
  cursize = Sidekiq.redis_pool.size
@@ -93,8 +115,8 @@ module Sidekiq
93
115
  begin
94
116
  launcher.run
95
117
 
96
- while (readable_io = IO.select([self_read]))
97
- signal = readable_io.first[0].gets.strip
118
+ while self_read.wait_readable
119
+ signal = self_read.gets.strip
98
120
  handle_signal(signal)
99
121
  end
100
122
  rescue Interrupt
@@ -228,8 +250,7 @@ module Sidekiq
228
250
  opts = parse_config(opts[:config_file]).merge(opts) if opts[:config_file]
229
251
 
230
252
  # set defaults
231
- opts[:queues] = ["default"] if opts[:queues].nil? || opts[:queues].empty?
232
- opts[:strict] = true if opts[:strict].nil?
253
+ opts[:queues] = ["default"] if opts[:queues].nil?
233
254
  opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
234
255
 
235
256
  # merge with defaults
@@ -240,7 +261,7 @@ module Sidekiq
240
261
  Sidekiq.options
241
262
  end
242
263
 
243
- def boot_system
264
+ def boot_application
244
265
  ENV["RACK_ENV"] = ENV["RAILS_ENV"] = environment
245
266
 
246
267
  if File.directory?(options[:require])
@@ -274,7 +295,7 @@ module Sidekiq
274
295
  (File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
275
296
  logger.info "=================================================================="
276
297
  logger.info " Please point Sidekiq to a Rails application or a Ruby file "
277
- logger.info " to load your worker classes with -r [DIR|FILE]."
298
+ logger.info " to load your job classes with -r [DIR|FILE]."
278
299
  logger.info "=================================================================="
279
300
  logger.info @parser
280
301
  die(1)
@@ -315,7 +336,7 @@ module Sidekiq
315
336
  parse_queue opts, queue, weight
316
337
  end
317
338
 
318
- o.on "-r", "--require [PATH|DIR]", "Location of Rails application with workers or file to require" do |arg|
339
+ o.on "-r", "--require [PATH|DIR]", "Location of Rails application with jobs or file to require" do |arg|
319
340
  opts[:require] = arg
320
341
  end
321
342
 
@@ -359,7 +380,9 @@ module Sidekiq
359
380
  end
360
381
 
361
382
  def parse_config(path)
362
- opts = YAML.load(ERB.new(File.read(path)).result) || {}
383
+ erb = ERB.new(File.read(path))
384
+ erb.filename = File.expand_path(path)
385
+ opts = load_yaml(erb.result) || {}
363
386
 
364
387
  if opts.respond_to? :deep_symbolize_keys!
365
388
  opts.deep_symbolize_keys!
@@ -368,19 +391,30 @@ module Sidekiq
368
391
  end
369
392
 
370
393
  opts = opts.merge(opts.delete(environment.to_sym) || {})
394
+ opts.delete(:strict)
395
+
371
396
  parse_queues(opts, opts.delete(:queues) || [])
372
397
 
373
398
  opts
374
399
  end
375
400
 
401
+ def load_yaml(src)
402
+ if Psych::VERSION > "4.0"
403
+ YAML.safe_load(src, permitted_classes: [Symbol], aliases: true)
404
+ else
405
+ YAML.load(src)
406
+ end
407
+ end
408
+
376
409
  def parse_queues(opts, queues_and_weights)
377
410
  queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
378
411
  end
379
412
 
380
413
  def parse_queue(opts, queue, weight = nil)
381
414
  opts[:queues] ||= []
415
+ opts[:strict] = true if opts[:strict].nil?
382
416
  raise ArgumentError, "queues: #{queue} cannot be defined twice" if opts[:queues].include?(queue)
383
- [weight.to_i, 1].max.times { opts[:queues] << queue }
417
+ [weight.to_i, 1].max.times { opts[:queues] << queue.to_s }
384
418
  opts[:strict] = false if weight.to_i > 0
385
419
  end
386
420