sidekiq 4.2.2 → 6.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. checksums.yaml +5 -5
  2. data/Changes.md +516 -0
  3. data/LICENSE +2 -2
  4. data/README.md +23 -36
  5. data/bin/sidekiq +26 -2
  6. data/bin/sidekiqload +28 -38
  7. data/bin/sidekiqmon +8 -0
  8. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +1 -1
  9. data/lib/generators/sidekiq/templates/worker_test.rb.erb +2 -2
  10. data/lib/generators/sidekiq/worker_generator.rb +21 -13
  11. data/lib/sidekiq/api.rb +401 -243
  12. data/lib/sidekiq/cli.rb +228 -212
  13. data/lib/sidekiq/client.rb +76 -53
  14. data/lib/sidekiq/delay.rb +41 -0
  15. data/lib/sidekiq/exception_handler.rb +12 -16
  16. data/lib/sidekiq/extensions/action_mailer.rb +13 -22
  17. data/lib/sidekiq/extensions/active_record.rb +13 -10
  18. data/lib/sidekiq/extensions/class_methods.rb +14 -11
  19. data/lib/sidekiq/extensions/generic_proxy.rb +12 -4
  20. data/lib/sidekiq/fetch.rb +39 -31
  21. data/lib/sidekiq/job.rb +13 -0
  22. data/lib/sidekiq/job_logger.rb +63 -0
  23. data/lib/sidekiq/job_retry.rb +259 -0
  24. data/lib/sidekiq/launcher.rb +170 -71
  25. data/lib/sidekiq/logger.rb +166 -0
  26. data/lib/sidekiq/manager.rb +17 -20
  27. data/lib/sidekiq/middleware/chain.rb +20 -8
  28. data/lib/sidekiq/middleware/current_attributes.rb +52 -0
  29. data/lib/sidekiq/middleware/i18n.rb +5 -7
  30. data/lib/sidekiq/monitor.rb +133 -0
  31. data/lib/sidekiq/paginator.rb +18 -14
  32. data/lib/sidekiq/processor.rb +169 -78
  33. data/lib/sidekiq/rails.rb +41 -36
  34. data/lib/sidekiq/redis_connection.rb +65 -20
  35. data/lib/sidekiq/scheduled.rb +85 -34
  36. data/lib/sidekiq/sd_notify.rb +149 -0
  37. data/lib/sidekiq/systemd.rb +24 -0
  38. data/lib/sidekiq/testing/inline.rb +2 -1
  39. data/lib/sidekiq/testing.rb +52 -26
  40. data/lib/sidekiq/util.rb +48 -15
  41. data/lib/sidekiq/version.rb +2 -1
  42. data/lib/sidekiq/web/action.rb +15 -17
  43. data/lib/sidekiq/web/application.rb +114 -92
  44. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  45. data/lib/sidekiq/web/helpers.rb +151 -83
  46. data/lib/sidekiq/web/router.rb +27 -19
  47. data/lib/sidekiq/web.rb +85 -76
  48. data/lib/sidekiq/worker.rb +233 -43
  49. data/lib/sidekiq.rb +88 -64
  50. data/sidekiq.gemspec +24 -22
  51. data/web/assets/images/apple-touch-icon.png +0 -0
  52. data/web/assets/javascripts/application.js +86 -59
  53. data/web/assets/javascripts/dashboard.js +81 -85
  54. data/web/assets/stylesheets/application-dark.css +147 -0
  55. data/web/assets/stylesheets/application-rtl.css +242 -0
  56. data/web/assets/stylesheets/application.css +319 -141
  57. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  58. data/web/assets/stylesheets/bootstrap.css +2 -2
  59. data/web/locales/ar.yml +87 -0
  60. data/web/locales/de.yml +14 -2
  61. data/web/locales/en.yml +8 -1
  62. data/web/locales/es.yml +22 -5
  63. data/web/locales/fa.yml +80 -0
  64. data/web/locales/fr.yml +10 -3
  65. data/web/locales/he.yml +79 -0
  66. data/web/locales/ja.yml +12 -4
  67. data/web/locales/lt.yml +83 -0
  68. data/web/locales/pl.yml +4 -4
  69. data/web/locales/ru.yml +4 -0
  70. data/web/locales/ur.yml +80 -0
  71. data/web/locales/vi.yml +83 -0
  72. data/web/views/_footer.erb +5 -2
  73. data/web/views/_job_info.erb +4 -3
  74. data/web/views/_nav.erb +4 -18
  75. data/web/views/_paging.erb +1 -1
  76. data/web/views/_poll_link.erb +2 -5
  77. data/web/views/_summary.erb +7 -7
  78. data/web/views/busy.erb +60 -22
  79. data/web/views/dashboard.erb +23 -15
  80. data/web/views/dead.erb +3 -3
  81. data/web/views/layout.erb +14 -3
  82. data/web/views/morgue.erb +19 -12
  83. data/web/views/queue.erb +24 -14
  84. data/web/views/queues.erb +14 -4
  85. data/web/views/retries.erb +22 -13
  86. data/web/views/retry.erb +4 -4
  87. data/web/views/scheduled.erb +7 -4
  88. metadata +44 -194
  89. data/.github/contributing.md +0 -32
  90. data/.github/issue_template.md +0 -4
  91. data/.gitignore +0 -12
  92. data/.travis.yml +0 -12
  93. data/3.0-Upgrade.md +0 -70
  94. data/4.0-Upgrade.md +0 -53
  95. data/COMM-LICENSE +0 -95
  96. data/Ent-Changes.md +0 -146
  97. data/Gemfile +0 -29
  98. data/Pro-2.0-Upgrade.md +0 -138
  99. data/Pro-3.0-Upgrade.md +0 -44
  100. data/Pro-Changes.md +0 -570
  101. data/Rakefile +0 -9
  102. data/bin/sidekiqctl +0 -99
  103. data/code_of_conduct.md +0 -50
  104. data/lib/sidekiq/core_ext.rb +0 -106
  105. data/lib/sidekiq/logging.rb +0 -106
  106. data/lib/sidekiq/middleware/server/active_record.rb +0 -13
  107. data/lib/sidekiq/middleware/server/logging.rb +0 -40
  108. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
  109. data/test/config.yml +0 -9
  110. data/test/env_based_config.yml +0 -11
  111. data/test/fake_env.rb +0 -1
  112. data/test/fixtures/en.yml +0 -2
  113. data/test/helper.rb +0 -75
  114. data/test/test_actors.rb +0 -138
  115. data/test/test_api.rb +0 -528
  116. data/test/test_cli.rb +0 -418
  117. data/test/test_client.rb +0 -266
  118. data/test/test_exception_handler.rb +0 -56
  119. data/test/test_extensions.rb +0 -127
  120. data/test/test_fetch.rb +0 -50
  121. data/test/test_launcher.rb +0 -95
  122. data/test/test_logging.rb +0 -35
  123. data/test/test_manager.rb +0 -50
  124. data/test/test_middleware.rb +0 -158
  125. data/test/test_processor.rb +0 -201
  126. data/test/test_rails.rb +0 -22
  127. data/test/test_redis_connection.rb +0 -132
  128. data/test/test_retry.rb +0 -326
  129. data/test/test_retry_exhausted.rb +0 -149
  130. data/test/test_scheduled.rb +0 -115
  131. data/test/test_scheduling.rb +0 -50
  132. data/test/test_sidekiq.rb +0 -107
  133. data/test/test_testing.rb +0 -143
  134. data/test/test_testing_fake.rb +0 -357
  135. data/test/test_testing_inline.rb +0 -94
  136. data/test/test_util.rb +0 -13
  137. data/test/test_web.rb +0 -666
  138. data/test/test_web_helpers.rb +0 -54
@@ -1,35 +1,56 @@
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
+ LUA_ZPOPBYSCORE = <<~LUA
13
+ local key, now = KEYS[1], ARGV[1]
14
+ local jobs = redis.call("zrangebyscore", key, "-inf", now, "limit", 0, 1)
15
+ if jobs[1] then
16
+ redis.call("zrem", key, jobs[1])
17
+ return jobs[1]
18
+ end
19
+ LUA
20
+
21
+ def initialize
22
+ @lua_zpopbyscore_sha = nil
23
+ end
24
+
25
+ def enqueue_jobs(now = Time.now.to_f.to_s, sorted_sets = SETS)
12
26
  # A job's "score" in Redis is the time at which it should be processed.
13
27
  # Just check Redis for the set of jobs with a timestamp before now.
14
28
  Sidekiq.redis do |conn|
15
29
  sorted_sets.each do |sorted_set|
16
- # Get the next item in the queue if it's score (time to execute) is <= now.
30
+ # Get next item in the queue with score (time to execute) <= now.
17
31
  # We need to go through the list one at a time to reduce the risk of something
18
32
  # going wrong between the time jobs are popped from the scheduled queue and when
19
33
  # they are pushed onto a work queue and losing the jobs.
20
- while job = conn.zrangebyscore(sorted_set, '-inf'.freeze, 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}" }
28
- end
34
+ while (job = zpopbyscore(conn, keys: [sorted_set], argv: [now]))
35
+ Sidekiq::Client.push(Sidekiq.load_json(job))
36
+ Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
29
37
  end
30
38
  end
31
39
  end
32
40
  end
41
+
42
+ private
43
+
44
+ def zpopbyscore(conn, keys: nil, argv: nil)
45
+ @lua_zpopbyscore_sha = conn.script(:load, LUA_ZPOPBYSCORE) if @lua_zpopbyscore_sha.nil?
46
+
47
+ conn.evalsha(@lua_zpopbyscore_sha, keys: keys, argv: argv)
48
+ rescue Redis::CommandError => e
49
+ raise unless e.message.start_with?("NOSCRIPT")
50
+
51
+ @lua_zpopbyscore_sha = nil
52
+ retry
53
+ end
33
54
  end
34
55
 
35
56
  ##
@@ -47,6 +68,7 @@ module Sidekiq
47
68
  @sleeper = ConnectionPool::TimedStack.new
48
69
  @done = false
49
70
  @thread = nil
71
+ @count_calls = 0
50
72
  end
51
73
 
52
74
  # Shut down this instance, will pause until the thread is dead.
@@ -61,28 +83,24 @@ module Sidekiq
61
83
  end
62
84
 
63
85
  def start
64
- @thread ||= safe_thread("scheduler") do
86
+ @thread ||= safe_thread("scheduler") {
65
87
  initial_wait
66
88
 
67
- while !@done
89
+ until @done
68
90
  enqueue
69
91
  wait
70
92
  end
71
93
  Sidekiq.logger.info("Scheduler exiting...")
72
- end
94
+ }
73
95
  end
74
96
 
75
97
  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
- ex.backtrace.each do |bt|
83
- logger.error(bt)
84
- end
85
- end
98
+ @enq.enqueue_jobs
99
+ rescue => ex
100
+ # Most likely a problem with redis networking.
101
+ # Punt and try again at the next interval
102
+ logger.error ex.message
103
+ handle_exception(ex)
86
104
  end
87
105
 
88
106
  private
@@ -95,13 +113,38 @@ module Sidekiq
95
113
  # if poll_interval_average hasn't been calculated yet, we can
96
114
  # raise an error trying to reach Redis.
97
115
  logger.error ex.message
98
- logger.error ex.backtrace.first
116
+ handle_exception(ex)
99
117
  sleep 5
100
118
  end
101
119
 
102
- # Calculates a random interval that is ±50% the desired average.
103
120
  def random_poll_interval
104
- poll_interval_average * rand + poll_interval_average.to_f / 2
121
+ # We want one Sidekiq process to schedule jobs every N seconds. We have M processes
122
+ # and **don't** want to coordinate.
123
+ #
124
+ # So in N*M second timespan, we want each process to schedule once. The basic loop is:
125
+ #
126
+ # * sleep a random amount within that N*M timespan
127
+ # * wake up and schedule
128
+ #
129
+ # We want to avoid one edge case: imagine a set of 2 processes, scheduling every 5 seconds,
130
+ # so N*M = 10. Each process decides to randomly sleep 8 seconds, now we've failed to meet
131
+ # that 5 second average. Thankfully each schedule cycle will sleep randomly so the next
132
+ # iteration could see each process sleep for 1 second, undercutting our average.
133
+ #
134
+ # So below 10 processes, we special case and ensure the processes sleep closer to the average.
135
+ # In the example above, each process should schedule every 10 seconds on average. We special
136
+ # case smaller clusters to add 50% so they would sleep somewhere between 5 and 15 seconds.
137
+ # As we run more processes, the scheduling interval average will approach an even spread
138
+ # between 0 and poll interval so we don't need this artifical boost.
139
+ #
140
+ if process_count < 10
141
+ # For small clusters, calculate a random interval that is ±50% the desired average.
142
+ poll_interval_average * rand + poll_interval_average.to_f / 2
143
+ else
144
+ # With 10+ processes, we should have enough randomness to get decent polling
145
+ # across the entire timespan
146
+ poll_interval_average * rand
147
+ end
105
148
  end
106
149
 
107
150
  # We do our best to tune the poll interval to the size of the active Sidekiq
@@ -125,9 +168,18 @@ module Sidekiq
125
168
  # This minimizes a single point of failure by dispersing check-ins but without taxing
126
169
  # Redis if you run many Sidekiq processes.
127
170
  def scaled_poll_interval
128
- pcount = Sidekiq::ProcessSet.new.size
171
+ process_count * Sidekiq.options[:average_scheduled_poll_interval]
172
+ end
173
+
174
+ def process_count
175
+ # The work buried within Sidekiq::ProcessSet#cleanup can be
176
+ # expensive at scale. Cut it down by 90% with this counter.
177
+ # NB: This method is only called by the scheduler thread so we
178
+ # don't need to worry about the thread safety of +=.
179
+ pcount = Sidekiq::ProcessSet.new(@count_calls % 10 == 0).size
129
180
  pcount = 1 if pcount == 0
130
- pcount * Sidekiq.options[:average_scheduled_poll_interval]
181
+ @count_calls += 1
182
+ pcount
131
183
  end
132
184
 
133
185
  def initial_wait
@@ -141,7 +193,6 @@ module Sidekiq
141
193
  @sleeper.pop(total)
142
194
  rescue Timeout::Error
143
195
  end
144
-
145
196
  end
146
197
  end
147
198
  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
@@ -55,6 +55,15 @@ module Sidekiq
55
55
  yield @server_chain if block_given?
56
56
  @server_chain
57
57
  end
58
+
59
+ def constantize(str)
60
+ names = str.split("::")
61
+ names.shift if names.empty? || names.first.empty?
62
+
63
+ names.inject(Object) do |constant, name|
64
+ constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
65
+ end
66
+ end
58
67
  end
59
68
  end
60
69
 
@@ -63,29 +72,31 @@ module Sidekiq
63
72
 
64
73
  class EmptyQueueError < RuntimeError; end
65
74
 
66
- class Client
67
- alias_method :raw_push_real, :raw_push
68
-
75
+ module TestingClient
69
76
  def raw_push(payloads)
70
77
  if Sidekiq::Testing.fake?
71
78
  payloads.each do |job|
72
- Queues.push(job['queue'], job['class'], Sidekiq.load_json(Sidekiq.dump_json(job)))
79
+ job = Sidekiq.load_json(Sidekiq.dump_json(job))
80
+ job["enqueued_at"] = Time.now.to_f unless job["at"]
81
+ Queues.push(job["queue"], job["class"], job)
73
82
  end
74
83
  true
75
84
  elsif Sidekiq::Testing.inline?
76
85
  payloads.each do |job|
77
- klass = job['class'].constantize
78
- job['id'] ||= SecureRandom.hex(12)
86
+ klass = Sidekiq::Testing.constantize(job["class"])
87
+ job["id"] ||= SecureRandom.hex(12)
79
88
  job_hash = Sidekiq.load_json(Sidekiq.dump_json(job))
80
89
  klass.process_job(job_hash)
81
90
  end
82
91
  true
83
92
  else
84
- raw_push_real(payloads)
93
+ super
85
94
  end
86
95
  end
87
96
  end
88
97
 
98
+ Sidekiq::Client.prepend TestingClient
99
+
89
100
  module Queues
90
101
  ##
91
102
  # The Queues class is only for testing the fake queue implementation.
@@ -244,27 +255,26 @@ module Sidekiq
244
255
  # Then I should receive a welcome email to "foo@example.com"
245
256
  #
246
257
  module ClassMethods
247
-
248
258
  # Queue for this worker
249
259
  def queue
250
- self.sidekiq_options["queue"]
260
+ get_sidekiq_options["queue"]
251
261
  end
252
262
 
253
263
  # Jobs queued for this worker
254
264
  def jobs
255
- Queues.jobs_by_worker[self.to_s]
265
+ Queues.jobs_by_worker[to_s]
256
266
  end
257
267
 
258
268
  # Clear all jobs for this worker
259
269
  def clear
260
- Queues.clear_for(queue, self.to_s)
270
+ Queues.clear_for(queue, to_s)
261
271
  end
262
272
 
263
273
  # Drain and run all jobs for this worker
264
274
  def drain
265
275
  while jobs.any?
266
276
  next_job = jobs.first
267
- Queues.delete_for(next_job["jid"], next_job["queue"], self.to_s)
277
+ Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
268
278
  process_job(next_job)
269
279
  end
270
280
  end
@@ -273,16 +283,16 @@ module Sidekiq
273
283
  def perform_one
274
284
  raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
275
285
  next_job = jobs.first
276
- Queues.delete_for(next_job["jid"], queue, self.to_s)
286
+ Queues.delete_for(next_job["jid"], queue, to_s)
277
287
  process_job(next_job)
278
288
  end
279
289
 
280
290
  def process_job(job)
281
291
  worker = new
282
- worker.jid = job['jid']
283
- worker.bid = job['bid'] if worker.respond_to?(:bid=)
284
- Sidekiq::Testing.server_middleware.invoke(worker, job, job['queue']) do
285
- 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"])
286
296
  end
287
297
  end
288
298
 
@@ -307,10 +317,26 @@ module Sidekiq
307
317
  worker_classes = jobs.map { |job| job["class"] }.uniq
308
318
 
309
319
  worker_classes.each do |worker_class|
310
- worker_class.constantize.drain
320
+ Sidekiq::Testing.constantize(worker_class).drain
311
321
  end
312
322
  end
313
323
  end
314
324
  end
315
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)
338
+ end
339
+
340
+ if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING
341
+ warn("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.", uplevel: 1)
316
342
  end
data/lib/sidekiq/util.rb CHANGED
@@ -1,27 +1,54 @@
1
1
  # frozen_string_literal: true
2
- require 'socket'
3
- require 'securerandom'
4
- require 'sidekiq/exception_handler'
5
- require 'sidekiq/core_ext'
2
+
3
+ require "forwardable"
4
+ require "socket"
5
+ require "securerandom"
6
+ require "sidekiq/exception_handler"
6
7
 
7
8
  module Sidekiq
8
9
  ##
9
10
  # This module is part of Sidekiq core and not intended for extensions.
10
11
  #
12
+
13
+ class RingBuffer
14
+ include Enumerable
15
+ extend Forwardable
16
+ def_delegators :@buf, :[], :each, :size
17
+
18
+ def initialize(size, default = 0)
19
+ @size = size
20
+ @buf = Array.new(size, default)
21
+ @index = 0
22
+ end
23
+
24
+ def <<(element)
25
+ @buf[@index % @size] = element
26
+ @index += 1
27
+ element
28
+ end
29
+
30
+ def buffer
31
+ @buf
32
+ end
33
+
34
+ def reset(default = 0)
35
+ @buf.fill(default)
36
+ end
37
+ end
38
+
11
39
  module Util
12
40
  include ExceptionHandler
13
41
 
14
- EXPIRY = 60 * 60 * 24
15
-
16
42
  def watchdog(last_words)
17
43
  yield
18
44
  rescue Exception => ex
19
- handle_exception(ex, { context: last_words })
45
+ handle_exception(ex, {context: last_words})
20
46
  raise ex
21
47
  end
22
48
 
23
49
  def safe_thread(name, &block)
24
50
  Thread.new do
51
+ Thread.current.name = name
25
52
  watchdog(name, &block)
26
53
  end
27
54
  end
@@ -34,8 +61,12 @@ module Sidekiq
34
61
  Sidekiq.redis(&block)
35
62
  end
36
63
 
64
+ def tid
65
+ Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
66
+ end
67
+
37
68
  def hostname
38
- ENV['DYNO'] || Socket.gethostname
69
+ ENV["DYNO"] || Socket.gethostname
39
70
  end
40
71
 
41
72
  def process_nonce
@@ -43,18 +74,20 @@ module Sidekiq
43
74
  end
44
75
 
45
76
  def identity
46
- @@identity ||= "#{hostname}:#{$$}:#{process_nonce}"
77
+ @@identity ||= "#{hostname}:#{::Process.pid}:#{process_nonce}"
47
78
  end
48
79
 
49
- def fire_event(event, reverse=false)
80
+ def fire_event(event, options = {})
81
+ reverse = options[:reverse]
82
+ reraise = options[:reraise]
83
+
50
84
  arr = Sidekiq.options[:lifecycle_events][event]
51
85
  arr.reverse! if reverse
52
86
  arr.each do |block|
53
- begin
54
- block.call
55
- rescue => ex
56
- handle_exception(ex, { event: event })
57
- end
87
+ block.call
88
+ rescue => ex
89
+ handle_exception(ex, {context: "Exception during Sidekiq lifecycle event.", event: event})
90
+ raise ex if reraise
58
91
  end
59
92
  arr.clear
60
93
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Sidekiq
3
- VERSION = "4.2.2"
4
+ VERSION = "6.3.1"
4
5
  end