sidekiq 3.4.1 → 7.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (235) hide show
  1. checksums.yaml +5 -5
  2. data/Changes.md +1118 -4
  3. data/LICENSE.txt +9 -0
  4. data/README.md +55 -47
  5. data/bin/multi_queue_bench +271 -0
  6. data/bin/sidekiq +26 -3
  7. data/bin/sidekiqload +247 -0
  8. data/bin/sidekiqmon +11 -0
  9. data/lib/generators/sidekiq/job_generator.rb +57 -0
  10. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  11. data/lib/generators/sidekiq/templates/job_spec.rb.erb +6 -0
  12. data/lib/generators/sidekiq/templates/job_test.rb.erb +8 -0
  13. data/lib/sidekiq/api.rb +714 -312
  14. data/lib/sidekiq/capsule.rb +130 -0
  15. data/lib/sidekiq/cli.rb +275 -241
  16. data/lib/sidekiq/client.rb +141 -110
  17. data/lib/sidekiq/component.rb +68 -0
  18. data/lib/sidekiq/config.rb +291 -0
  19. data/lib/sidekiq/deploy.rb +62 -0
  20. data/lib/sidekiq/embedded.rb +61 -0
  21. data/lib/sidekiq/fetch.rb +53 -121
  22. data/lib/sidekiq/iterable_job.rb +53 -0
  23. data/lib/sidekiq/job/interrupt_handler.rb +22 -0
  24. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  25. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  26. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  27. data/lib/sidekiq/job/iterable.rb +231 -0
  28. data/lib/sidekiq/job.rb +385 -0
  29. data/lib/sidekiq/job_logger.rb +64 -0
  30. data/lib/sidekiq/job_retry.rb +305 -0
  31. data/lib/sidekiq/job_util.rb +107 -0
  32. data/lib/sidekiq/launcher.rb +241 -66
  33. data/lib/sidekiq/logger.rb +131 -0
  34. data/lib/sidekiq/manager.rb +91 -192
  35. data/lib/sidekiq/metrics/query.rb +156 -0
  36. data/lib/sidekiq/metrics/shared.rb +95 -0
  37. data/lib/sidekiq/metrics/tracking.rb +140 -0
  38. data/lib/sidekiq/middleware/chain.rb +114 -56
  39. data/lib/sidekiq/middleware/current_attributes.rb +111 -0
  40. data/lib/sidekiq/middleware/i18n.rb +8 -7
  41. data/lib/sidekiq/middleware/modules.rb +21 -0
  42. data/lib/sidekiq/monitor.rb +146 -0
  43. data/lib/sidekiq/paginator.rb +29 -16
  44. data/lib/sidekiq/processor.rb +248 -112
  45. data/lib/sidekiq/rails.rb +61 -27
  46. data/lib/sidekiq/redis_client_adapter.rb +114 -0
  47. data/lib/sidekiq/redis_connection.rb +68 -48
  48. data/lib/sidekiq/ring_buffer.rb +29 -0
  49. data/lib/sidekiq/scheduled.rb +173 -52
  50. data/lib/sidekiq/sd_notify.rb +149 -0
  51. data/lib/sidekiq/systemd.rb +24 -0
  52. data/lib/sidekiq/testing/inline.rb +7 -5
  53. data/lib/sidekiq/testing.rb +206 -65
  54. data/lib/sidekiq/transaction_aware_client.rb +51 -0
  55. data/lib/sidekiq/version.rb +4 -1
  56. data/lib/sidekiq/web/action.rb +99 -0
  57. data/lib/sidekiq/web/application.rb +479 -0
  58. data/lib/sidekiq/web/csrf_protection.rb +183 -0
  59. data/lib/sidekiq/web/helpers.rb +415 -0
  60. data/lib/sidekiq/web/router.rb +104 -0
  61. data/lib/sidekiq/web.rb +158 -200
  62. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  63. data/lib/sidekiq.rb +100 -132
  64. data/sidekiq.gemspec +27 -23
  65. data/web/assets/images/apple-touch-icon.png +0 -0
  66. data/web/assets/images/favicon.ico +0 -0
  67. data/web/assets/javascripts/application.js +177 -72
  68. data/web/assets/javascripts/base-charts.js +106 -0
  69. data/web/assets/javascripts/chart.min.js +13 -0
  70. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  71. data/web/assets/javascripts/dashboard-charts.js +192 -0
  72. data/web/assets/javascripts/dashboard.js +37 -286
  73. data/web/assets/javascripts/metrics.js +298 -0
  74. data/web/assets/stylesheets/application-dark.css +147 -0
  75. data/web/assets/stylesheets/application-rtl.css +163 -0
  76. data/web/assets/stylesheets/application.css +228 -247
  77. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  78. data/web/assets/stylesheets/bootstrap.css +4 -8
  79. data/web/locales/ar.yml +87 -0
  80. data/web/locales/cs.yml +62 -52
  81. data/web/locales/da.yml +60 -53
  82. data/web/locales/de.yml +65 -53
  83. data/web/locales/el.yml +43 -24
  84. data/web/locales/en.yml +86 -61
  85. data/web/locales/es.yml +70 -53
  86. data/web/locales/fa.yml +80 -0
  87. data/web/locales/fr.yml +86 -56
  88. data/web/locales/gd.yml +99 -0
  89. data/web/locales/he.yml +80 -0
  90. data/web/locales/hi.yml +59 -59
  91. data/web/locales/it.yml +53 -53
  92. data/web/locales/ja.yml +78 -56
  93. data/web/locales/ko.yml +52 -52
  94. data/web/locales/lt.yml +83 -0
  95. data/web/locales/{no.yml → nb.yml} +62 -54
  96. data/web/locales/nl.yml +52 -52
  97. data/web/locales/pl.yml +45 -45
  98. data/web/locales/pt-br.yml +83 -55
  99. data/web/locales/pt.yml +51 -51
  100. data/web/locales/ru.yml +68 -60
  101. data/web/locales/sv.yml +53 -53
  102. data/web/locales/ta.yml +60 -60
  103. data/web/locales/tr.yml +101 -0
  104. data/web/locales/uk.yml +77 -0
  105. data/web/locales/ur.yml +80 -0
  106. data/web/locales/vi.yml +83 -0
  107. data/web/locales/zh-cn.yml +43 -16
  108. data/web/locales/zh-tw.yml +42 -8
  109. data/web/views/_footer.erb +22 -9
  110. data/web/views/_job_info.erb +27 -6
  111. data/web/views/_metrics_period_select.erb +12 -0
  112. data/web/views/_nav.erb +8 -22
  113. data/web/views/_paging.erb +3 -1
  114. data/web/views/_poll_link.erb +4 -0
  115. data/web/views/_summary.erb +7 -7
  116. data/web/views/busy.erb +91 -31
  117. data/web/views/dashboard.erb +52 -22
  118. data/web/views/dead.erb +5 -4
  119. data/web/views/filtering.erb +7 -0
  120. data/web/views/layout.erb +19 -7
  121. data/web/views/metrics.erb +91 -0
  122. data/web/views/metrics_for_job.erb +59 -0
  123. data/web/views/morgue.erb +26 -20
  124. data/web/views/queue.erb +36 -25
  125. data/web/views/queues.erb +24 -7
  126. data/web/views/retries.erb +29 -21
  127. data/web/views/retry.erb +6 -5
  128. data/web/views/scheduled.erb +20 -17
  129. data/web/views/scheduled_job_info.erb +2 -1
  130. metadata +101 -232
  131. data/.gitignore +0 -12
  132. data/.travis.yml +0 -16
  133. data/3.0-Upgrade.md +0 -70
  134. data/COMM-LICENSE +0 -85
  135. data/Contributing.md +0 -32
  136. data/Gemfile +0 -22
  137. data/LICENSE +0 -9
  138. data/Pro-2.0-Upgrade.md +0 -138
  139. data/Pro-Changes.md +0 -412
  140. data/Rakefile +0 -9
  141. data/bin/sidekiqctl +0 -93
  142. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +0 -6
  143. data/lib/generators/sidekiq/templates/worker_test.rb.erb +0 -8
  144. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  145. data/lib/sidekiq/actor.rb +0 -39
  146. data/lib/sidekiq/core_ext.rb +0 -105
  147. data/lib/sidekiq/exception_handler.rb +0 -30
  148. data/lib/sidekiq/extensions/action_mailer.rb +0 -56
  149. data/lib/sidekiq/extensions/active_record.rb +0 -39
  150. data/lib/sidekiq/extensions/class_methods.rb +0 -39
  151. data/lib/sidekiq/extensions/generic_proxy.rb +0 -24
  152. data/lib/sidekiq/logging.rb +0 -104
  153. data/lib/sidekiq/middleware/server/active_record.rb +0 -13
  154. data/lib/sidekiq/middleware/server/logging.rb +0 -35
  155. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -206
  156. data/lib/sidekiq/util.rb +0 -55
  157. data/lib/sidekiq/web_helpers.rb +0 -234
  158. data/lib/sidekiq/worker.rb +0 -89
  159. data/test/config.yml +0 -9
  160. data/test/env_based_config.yml +0 -11
  161. data/test/fake_env.rb +0 -0
  162. data/test/fixtures/en.yml +0 -2
  163. data/test/helper.rb +0 -39
  164. data/test/test_api.rb +0 -494
  165. data/test/test_cli.rb +0 -365
  166. data/test/test_client.rb +0 -269
  167. data/test/test_exception_handler.rb +0 -55
  168. data/test/test_extensions.rb +0 -120
  169. data/test/test_fetch.rb +0 -104
  170. data/test/test_logging.rb +0 -34
  171. data/test/test_manager.rb +0 -164
  172. data/test/test_middleware.rb +0 -159
  173. data/test/test_processor.rb +0 -166
  174. data/test/test_redis_connection.rb +0 -127
  175. data/test/test_retry.rb +0 -373
  176. data/test/test_scheduled.rb +0 -120
  177. data/test/test_scheduling.rb +0 -71
  178. data/test/test_sidekiq.rb +0 -69
  179. data/test/test_testing.rb +0 -82
  180. data/test/test_testing_fake.rb +0 -271
  181. data/test/test_testing_inline.rb +0 -93
  182. data/test/test_web.rb +0 -594
  183. data/test/test_web_helpers.rb +0 -52
  184. data/test/test_worker_generator.rb +0 -17
  185. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  186. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  187. data/web/assets/images/status/active.png +0 -0
  188. data/web/assets/images/status/idle.png +0 -0
  189. data/web/assets/javascripts/locales/README.md +0 -27
  190. data/web/assets/javascripts/locales/jquery.timeago.ar.js +0 -96
  191. data/web/assets/javascripts/locales/jquery.timeago.bg.js +0 -18
  192. data/web/assets/javascripts/locales/jquery.timeago.bs.js +0 -49
  193. data/web/assets/javascripts/locales/jquery.timeago.ca.js +0 -18
  194. data/web/assets/javascripts/locales/jquery.timeago.cs.js +0 -18
  195. data/web/assets/javascripts/locales/jquery.timeago.cy.js +0 -20
  196. data/web/assets/javascripts/locales/jquery.timeago.da.js +0 -18
  197. data/web/assets/javascripts/locales/jquery.timeago.de.js +0 -18
  198. data/web/assets/javascripts/locales/jquery.timeago.el.js +0 -18
  199. data/web/assets/javascripts/locales/jquery.timeago.en-short.js +0 -20
  200. data/web/assets/javascripts/locales/jquery.timeago.en.js +0 -20
  201. data/web/assets/javascripts/locales/jquery.timeago.es.js +0 -18
  202. data/web/assets/javascripts/locales/jquery.timeago.et.js +0 -18
  203. data/web/assets/javascripts/locales/jquery.timeago.fa.js +0 -22
  204. data/web/assets/javascripts/locales/jquery.timeago.fi.js +0 -28
  205. data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +0 -16
  206. data/web/assets/javascripts/locales/jquery.timeago.fr.js +0 -17
  207. data/web/assets/javascripts/locales/jquery.timeago.he.js +0 -18
  208. data/web/assets/javascripts/locales/jquery.timeago.hr.js +0 -49
  209. data/web/assets/javascripts/locales/jquery.timeago.hu.js +0 -18
  210. data/web/assets/javascripts/locales/jquery.timeago.hy.js +0 -18
  211. data/web/assets/javascripts/locales/jquery.timeago.id.js +0 -18
  212. data/web/assets/javascripts/locales/jquery.timeago.it.js +0 -16
  213. data/web/assets/javascripts/locales/jquery.timeago.ja.js +0 -19
  214. data/web/assets/javascripts/locales/jquery.timeago.ko.js +0 -17
  215. data/web/assets/javascripts/locales/jquery.timeago.lt.js +0 -20
  216. data/web/assets/javascripts/locales/jquery.timeago.mk.js +0 -20
  217. data/web/assets/javascripts/locales/jquery.timeago.nl.js +0 -20
  218. data/web/assets/javascripts/locales/jquery.timeago.no.js +0 -18
  219. data/web/assets/javascripts/locales/jquery.timeago.pl.js +0 -31
  220. data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +0 -16
  221. data/web/assets/javascripts/locales/jquery.timeago.pt.js +0 -16
  222. data/web/assets/javascripts/locales/jquery.timeago.ro.js +0 -18
  223. data/web/assets/javascripts/locales/jquery.timeago.rs.js +0 -49
  224. data/web/assets/javascripts/locales/jquery.timeago.ru.js +0 -34
  225. data/web/assets/javascripts/locales/jquery.timeago.sk.js +0 -18
  226. data/web/assets/javascripts/locales/jquery.timeago.sl.js +0 -44
  227. data/web/assets/javascripts/locales/jquery.timeago.sv.js +0 -18
  228. data/web/assets/javascripts/locales/jquery.timeago.th.js +0 -20
  229. data/web/assets/javascripts/locales/jquery.timeago.tr.js +0 -16
  230. data/web/assets/javascripts/locales/jquery.timeago.uk.js +0 -34
  231. data/web/assets/javascripts/locales/jquery.timeago.uz.js +0 -19
  232. data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +0 -20
  233. data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +0 -20
  234. data/web/views/_poll.erb +0 -10
  235. /data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
@@ -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,8 +1,10 @@
1
- require 'sidekiq/testing'
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/testing"
2
4
 
3
5
  ##
4
6
  # The Sidekiq inline infrastructure overrides perform_async so that it
5
- # actually calls perform instead. This allows workers to be run inline in a
7
+ # actually calls perform instead. This allows jobs to be run inline in a
6
8
  # testing environment.
7
9
  #
8
10
  # This is similar to `Resque.inline = true` functionality.
@@ -13,8 +15,8 @@ require 'sidekiq/testing'
13
15
  #
14
16
  # $external_variable = 0
15
17
  #
16
- # class ExternalWorker
17
- # include Sidekiq::Worker
18
+ # class ExternalJob
19
+ # include Sidekiq::Job
18
20
  #
19
21
  # def perform
20
22
  # $external_variable = 1
@@ -22,7 +24,7 @@ require 'sidekiq/testing'
22
24
  # end
23
25
  #
24
26
  # assert_equal 0, $external_variable
25
- # ExternalWorker.perform_async
27
+ # ExternalJob.perform_async
26
28
  # assert_equal 1, $external_variable
27
29
  #
28
30
  Sidekiq::Testing.inline!
@@ -1,26 +1,46 @@
1
- require 'securerandom'
2
- require 'sidekiq'
1
+ # frozen_string_literal: true
3
2
 
4
- module Sidekiq
3
+ require "securerandom"
4
+ require "sidekiq"
5
5
 
6
+ module Sidekiq
6
7
  class Testing
8
+ class TestModeAlreadySetError < RuntimeError; end
7
9
  class << self
8
- attr_accessor :__test_mode
10
+ attr_accessor :__global_test_mode
9
11
 
12
+ # Calling without a block sets the global test mode, affecting
13
+ # all threads. Calling with a block only affects the current Thread.
10
14
  def __set_test_mode(mode)
11
15
  if block_given?
12
- current_mode = self.__test_mode
16
+ # Reentrant testing modes will lead to a rat's nest of code which is
17
+ # hard to reason about. You can set the testing mode once globally and
18
+ # you can override that global setting once per-thread.
19
+ raise TestModeAlreadySetError, "Nesting test modes is not supported" if __local_test_mode
20
+
21
+ self.__local_test_mode = mode
13
22
  begin
14
- self.__test_mode = mode
15
23
  yield
16
24
  ensure
17
- self.__test_mode = current_mode
25
+ self.__local_test_mode = nil
18
26
  end
19
27
  else
20
- self.__test_mode = mode
28
+ self.__global_test_mode = mode
21
29
  end
22
30
  end
23
31
 
32
+ def __test_mode
33
+ __local_test_mode || __global_test_mode
34
+ end
35
+
36
+ def __local_test_mode
37
+ Thread.current[:__sidekiq_test_mode]
38
+ end
39
+
40
+ def __local_test_mode=(value)
41
+ Thread.current[:__sidekiq_test_mode] = value
42
+ end
43
+
24
44
  def disable!(&block)
25
45
  __set_test_mode(:disable, &block)
26
46
  end
@@ -34,19 +54,25 @@ module Sidekiq
34
54
  end
35
55
 
36
56
  def enabled?
37
- self.__test_mode != :disable
57
+ __test_mode != :disable
38
58
  end
39
59
 
40
60
  def disabled?
41
- self.__test_mode == :disable
61
+ __test_mode == :disable
42
62
  end
43
63
 
44
64
  def fake?
45
- self.__test_mode == :fake
65
+ __test_mode == :fake
46
66
  end
47
67
 
48
68
  def inline?
49
- self.__test_mode == :inline
69
+ __test_mode == :inline
70
+ end
71
+
72
+ def server_middleware
73
+ @server_chain ||= Middleware::Chain.new(Sidekiq.default_configuration)
74
+ yield @server_chain if block_given?
75
+ @server_chain
50
76
  end
51
77
  end
52
78
  end
@@ -56,30 +82,134 @@ module Sidekiq
56
82
 
57
83
  class EmptyQueueError < RuntimeError; end
58
84
 
59
- class Client
60
- alias_method :raw_push_real, :raw_push
61
-
62
- def raw_push(payloads)
85
+ module TestingClient
86
+ def atomic_push(conn, payloads)
63
87
  if Sidekiq::Testing.fake?
64
88
  payloads.each do |job|
65
- job['class'].constantize.jobs << Sidekiq.load_json(Sidekiq.dump_json(job))
89
+ job = Sidekiq.load_json(Sidekiq.dump_json(job))
90
+ job["enqueued_at"] = Time.now.to_f unless job["at"]
91
+ Queues.push(job["queue"], job["class"], job)
66
92
  end
67
93
  true
68
94
  elsif Sidekiq::Testing.inline?
69
95
  payloads.each do |job|
70
- job['jid'] ||= SecureRandom.hex(12)
71
- klass = job['class'].constantize
72
- klass.jobs.unshift Sidekiq.load_json(Sidekiq.dump_json(job))
73
- klass.perform_one
96
+ klass = Object.const_get(job["class"])
97
+ job["id"] ||= SecureRandom.hex(12)
98
+ job_hash = Sidekiq.load_json(Sidekiq.dump_json(job))
99
+ klass.process_job(job_hash)
74
100
  end
75
101
  true
76
102
  else
77
- raw_push_real(payloads)
103
+ super
78
104
  end
79
105
  end
80
106
  end
81
107
 
82
- module Worker
108
+ Sidekiq::Client.prepend TestingClient
109
+
110
+ module Queues
111
+ ##
112
+ # The Queues class is only for testing the fake queue implementation.
113
+ # There are 2 data structures involved in tandem. This is due to the
114
+ # Rspec syntax of change(HardJob.jobs, :size). It keeps a reference
115
+ # to the array. Because the array was derived from a filter of the total
116
+ # jobs enqueued, it appeared as though the array didn't change.
117
+ #
118
+ # To solve this, we'll keep 2 hashes containing the jobs. One with keys based
119
+ # on the queue, and another with keys of the job type, so the array for
120
+ # HardJob.jobs is a straight reference to a real array.
121
+ #
122
+ # Queue-based hash:
123
+ #
124
+ # {
125
+ # "default"=>[
126
+ # {
127
+ # "class"=>"TestTesting::HardJob",
128
+ # "args"=>[1, 2],
129
+ # "retry"=>true,
130
+ # "queue"=>"default",
131
+ # "jid"=>"abc5b065c5c4b27fc1102833",
132
+ # "created_at"=>1447445554.419934
133
+ # }
134
+ # ]
135
+ # }
136
+ #
137
+ # Job-based hash:
138
+ #
139
+ # {
140
+ # "TestTesting::HardJob"=>[
141
+ # {
142
+ # "class"=>"TestTesting::HardJob",
143
+ # "args"=>[1, 2],
144
+ # "retry"=>true,
145
+ # "queue"=>"default",
146
+ # "jid"=>"abc5b065c5c4b27fc1102833",
147
+ # "created_at"=>1447445554.419934
148
+ # }
149
+ # ]
150
+ # }
151
+ #
152
+ # Example:
153
+ #
154
+ # require 'sidekiq/testing'
155
+ #
156
+ # assert_equal 0, Sidekiq::Queues["default"].size
157
+ # HardJob.perform_async(:something)
158
+ # assert_equal 1, Sidekiq::Queues["default"].size
159
+ # assert_equal :something, Sidekiq::Queues["default"].first['args'][0]
160
+ #
161
+ # You can also clear all jobs:
162
+ #
163
+ # assert_equal 0, Sidekiq::Queues["default"].size
164
+ # HardJob.perform_async(:something)
165
+ # Sidekiq::Queues.clear_all
166
+ # assert_equal 0, Sidekiq::Queues["default"].size
167
+ #
168
+ # This can be useful to make sure jobs don't linger between tests:
169
+ #
170
+ # RSpec.configure do |config|
171
+ # config.before(:each) do
172
+ # Sidekiq::Queues.clear_all
173
+ # end
174
+ # end
175
+ #
176
+ class << self
177
+ def [](queue)
178
+ jobs_by_queue[queue]
179
+ end
180
+
181
+ def push(queue, klass, job)
182
+ jobs_by_queue[queue] << job
183
+ jobs_by_class[klass] << job
184
+ end
185
+
186
+ def jobs_by_queue
187
+ @jobs_by_queue ||= Hash.new { |hash, key| hash[key] = [] }
188
+ end
189
+
190
+ def jobs_by_class
191
+ @jobs_by_class ||= Hash.new { |hash, key| hash[key] = [] }
192
+ end
193
+ alias_method :jobs_by_worker, :jobs_by_class
194
+
195
+ def delete_for(jid, queue, klass)
196
+ jobs_by_queue[queue.to_s].delete_if { |job| job["jid"] == jid }
197
+ jobs_by_class[klass].delete_if { |job| job["jid"] == jid }
198
+ end
199
+
200
+ def clear_for(queue, klass)
201
+ jobs_by_queue[queue.to_s].clear
202
+ jobs_by_class[klass].clear
203
+ end
204
+
205
+ def clear_all
206
+ jobs_by_queue.clear
207
+ jobs_by_class.clear
208
+ end
209
+ end
210
+ end
211
+
212
+ module Job
83
213
  ##
84
214
  # The Sidekiq testing infrastructure overrides perform_async
85
215
  # so that it does not actually touch the network. Instead it
@@ -93,78 +223,72 @@ module Sidekiq
93
223
  #
94
224
  # require 'sidekiq/testing'
95
225
  #
96
- # assert_equal 0, HardWorker.jobs.size
97
- # HardWorker.perform_async(:something)
98
- # assert_equal 1, HardWorker.jobs.size
99
- # assert_equal :something, HardWorker.jobs[0]['args'][0]
226
+ # assert_equal 0, HardJob.jobs.size
227
+ # HardJob.perform_async(:something)
228
+ # assert_equal 1, HardJob.jobs.size
229
+ # assert_equal :something, HardJob.jobs[0]['args'][0]
100
230
  #
101
- # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
102
- # MyMailer.delay.send_welcome_email('foo@example.com')
103
- # assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
231
+ # You can also clear and drain all job types:
104
232
  #
105
- # You can also clear and drain all workers' jobs:
106
- #
107
- # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
108
- # assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size
109
- #
110
- # MyMailer.delay.send_welcome_email('foo@example.com')
111
- # MyModel.delay.do_something_hard
112
- #
113
- # assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
114
- # assert_equal 1, Sidekiq::Extensions::DelayedModel.jobs.size
115
- #
116
- # Sidekiq::Worker.clear_all # or .drain_all
117
- #
118
- # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
119
- # assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size
233
+ # Sidekiq::Job.clear_all # or .drain_all
120
234
  #
121
235
  # This can be useful to make sure jobs don't linger between tests:
122
236
  #
123
237
  # RSpec.configure do |config|
124
238
  # config.before(:each) do
125
- # Sidekiq::Worker.clear_all
239
+ # Sidekiq::Job.clear_all
126
240
  # end
127
241
  # end
128
242
  #
129
243
  # or for acceptance testing, i.e. with cucumber:
130
244
  #
131
245
  # AfterStep do
132
- # Sidekiq::Worker.drain_all
246
+ # Sidekiq::Job.drain_all
133
247
  # end
134
248
  #
135
249
  # When I sign up as "foo@example.com"
136
250
  # Then I should receive a welcome email to "foo@example.com"
137
251
  #
138
252
  module ClassMethods
253
+ # Queue for this worker
254
+ def queue
255
+ get_sidekiq_options["queue"]
256
+ end
139
257
 
140
258
  # Jobs queued for this worker
141
259
  def jobs
142
- Worker.jobs[self]
260
+ Queues.jobs_by_class[to_s]
143
261
  end
144
262
 
145
263
  # Clear all jobs for this worker
146
264
  def clear
147
- jobs.clear
265
+ Queues.clear_for(queue, to_s)
148
266
  end
149
267
 
150
268
  # Drain and run all jobs for this worker
151
269
  def drain
152
- while job = jobs.shift do
153
- worker = new
154
- worker.jid = job['jid']
155
- worker.bid = job['bid'] if worker.respond_to?(:bid=)
156
- execute_job(worker, job['args'])
270
+ while jobs.any?
271
+ next_job = jobs.first
272
+ Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
273
+ process_job(next_job)
157
274
  end
158
275
  end
159
276
 
160
277
  # Pop out a single job and perform it
161
278
  def perform_one
162
279
  raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
163
- job = jobs.shift
164
- worker = new
165
- worker.jid = job['jid']
166
- worker.bid = job['bid'] if worker.respond_to?(:bid=)
167
- execute_job(worker, job['args'])
280
+ next_job = jobs.first
281
+ Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
282
+ process_job(next_job)
283
+ end
284
+
285
+ def process_job(job)
286
+ inst = new
287
+ inst.jid = job["jid"]
288
+ inst.bid = job["bid"] if inst.respond_to?(:bid=)
289
+ Sidekiq::Testing.server_middleware.invoke(inst, job, job["queue"]) do
290
+ execute_job(inst, job["args"])
291
+ end
168
292
  end
169
293
 
170
294
  def execute_job(worker, args)
@@ -174,20 +298,37 @@ module Sidekiq
174
298
 
175
299
  class << self
176
300
  def jobs # :nodoc:
177
- @jobs ||= Hash.new { |hash, key| hash[key] = [] }
301
+ Queues.jobs_by_queue.values.flatten
178
302
  end
179
303
 
180
- # Clear all queued jobs across all workers
304
+ # Clear all queued jobs
181
305
  def clear_all
182
- jobs.clear
306
+ Queues.clear_all
183
307
  end
184
308
 
185
- # Drain all queued jobs across all workers
309
+ # Drain (execute) all queued jobs
186
310
  def drain_all
187
- until jobs.values.all?(&:empty?) do
188
- jobs.keys.each(&:drain)
311
+ while jobs.any?
312
+ job_classes = jobs.map { |job| job["class"] }.uniq
313
+
314
+ job_classes.each do |job_class|
315
+ Object.const_get(job_class).drain
316
+ end
189
317
  end
190
318
  end
191
319
  end
192
320
  end
321
+
322
+ module TestingExtensions
323
+ def jobs_for(klass)
324
+ jobs.select do |job|
325
+ marshalled = job["args"][0]
326
+ marshalled.index(klass.to_s) && YAML.safe_load(marshalled)[0] == klass
327
+ end
328
+ end
329
+ end
330
+ end
331
+
332
+ if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING
333
+ warn("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.", uplevel: 1)
193
334
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "sidekiq/client"
5
+
6
+ module Sidekiq
7
+ class TransactionAwareClient
8
+ def initialize(pool: nil, config: nil)
9
+ @redis_client = Client.new(pool: pool, config: config)
10
+ end
11
+
12
+ def batching?
13
+ Thread.current[:sidekiq_batch]
14
+ end
15
+
16
+ def push(item)
17
+ # 6160 we can't support both Sidekiq::Batch and transactions.
18
+ return @redis_client.push(item) if batching?
19
+
20
+ # pre-allocate the JID so we can return it immediately and
21
+ # save it to the database as part of the transaction.
22
+ item["jid"] ||= SecureRandom.hex(12)
23
+ AfterCommitEverywhere.after_commit { @redis_client.push(item) }
24
+ item["jid"]
25
+ end
26
+
27
+ ##
28
+ # We don't provide transactionality for push_bulk because we don't want
29
+ # to hold potentially hundreds of thousands of job records in memory due to
30
+ # a long running enqueue process.
31
+ def push_bulk(items)
32
+ @redis_client.push_bulk(items)
33
+ end
34
+ end
35
+ end
36
+
37
+ ##
38
+ # Use `Sidekiq.transactional_push!` in your sidekiq.rb initializer
39
+ module Sidekiq
40
+ def self.transactional_push!
41
+ begin
42
+ require "after_commit_everywhere"
43
+ rescue LoadError
44
+ raise %q(You need to add `gem "after_commit_everywhere"` to your Gemfile to use Sidekiq's transactional client)
45
+ end
46
+
47
+ Sidekiq.default_job_options["client_class"] = Sidekiq::TransactionAwareClient
48
+ Sidekiq::JobUtil::TRANSIENT_ATTRIBUTES << "client_class"
49
+ true
50
+ end
51
+ end
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sidekiq
2
- VERSION = "3.4.1"
4
+ VERSION = "7.3.0"
5
+ MAJOR = 7
3
6
  end