sr-sidekiq 4.1.6

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 (186) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/3.0-Upgrade.md +70 -0
  4. data/4.0-Upgrade.md +50 -0
  5. data/COMM-LICENSE (sidekiq) +95 -0
  6. data/Changes.md +1241 -0
  7. data/Ent-Changes.md +112 -0
  8. data/Gemfile +29 -0
  9. data/LICENSE (sidekiq) +9 -0
  10. data/LICENSE (sr-sidekiq) +5 -0
  11. data/Pro-2.0-Upgrade.md +138 -0
  12. data/Pro-3.0-Upgrade.md +44 -0
  13. data/Pro-Changes.md +539 -0
  14. data/README.md +8 -0
  15. data/Rakefile +9 -0
  16. data/bin/sidekiq +18 -0
  17. data/bin/sidekiqctl +99 -0
  18. data/bin/sidekiqload +167 -0
  19. data/code_of_conduct.md +50 -0
  20. data/lib/generators/sidekiq/templates/worker.rb.erb +9 -0
  21. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +6 -0
  22. data/lib/generators/sidekiq/templates/worker_test.rb.erb +8 -0
  23. data/lib/generators/sidekiq/worker_generator.rb +49 -0
  24. data/lib/sidekiq.rb +237 -0
  25. data/lib/sidekiq/api.rb +844 -0
  26. data/lib/sidekiq/cli.rb +389 -0
  27. data/lib/sidekiq/client.rb +260 -0
  28. data/lib/sidekiq/core_ext.rb +106 -0
  29. data/lib/sidekiq/exception_handler.rb +31 -0
  30. data/lib/sidekiq/extensions/action_mailer.rb +57 -0
  31. data/lib/sidekiq/extensions/active_record.rb +40 -0
  32. data/lib/sidekiq/extensions/class_methods.rb +40 -0
  33. data/lib/sidekiq/extensions/generic_proxy.rb +25 -0
  34. data/lib/sidekiq/fetch.rb +81 -0
  35. data/lib/sidekiq/launcher.rb +160 -0
  36. data/lib/sidekiq/logging.rb +106 -0
  37. data/lib/sidekiq/manager.rb +137 -0
  38. data/lib/sidekiq/middleware/chain.rb +150 -0
  39. data/lib/sidekiq/middleware/i18n.rb +42 -0
  40. data/lib/sidekiq/middleware/server/active_record.rb +13 -0
  41. data/lib/sidekiq/middleware/server/logging.rb +40 -0
  42. data/lib/sidekiq/middleware/server/retry_jobs.rb +205 -0
  43. data/lib/sidekiq/paginator.rb +43 -0
  44. data/lib/sidekiq/processor.rb +186 -0
  45. data/lib/sidekiq/rails.rb +39 -0
  46. data/lib/sidekiq/redis_connection.rb +97 -0
  47. data/lib/sidekiq/scheduled.rb +146 -0
  48. data/lib/sidekiq/testing.rb +316 -0
  49. data/lib/sidekiq/testing/inline.rb +29 -0
  50. data/lib/sidekiq/util.rb +62 -0
  51. data/lib/sidekiq/version.rb +4 -0
  52. data/lib/sidekiq/web.rb +278 -0
  53. data/lib/sidekiq/web_helpers.rb +255 -0
  54. data/lib/sidekiq/worker.rb +121 -0
  55. data/sidekiq.gemspec +26 -0
  56. data/sr-sidekiq-4.1.3.gem +0 -0
  57. data/sr-sidekiq-4.1.4.gem +0 -0
  58. data/sr-sidekiq-4.1.5.gem +0 -0
  59. data/test/config.yml +9 -0
  60. data/test/env_based_config.yml +11 -0
  61. data/test/fake_env.rb +1 -0
  62. data/test/fixtures/en.yml +2 -0
  63. data/test/helper.rb +75 -0
  64. data/test/test_actors.rb +138 -0
  65. data/test/test_api.rb +528 -0
  66. data/test/test_cli.rb +406 -0
  67. data/test/test_client.rb +262 -0
  68. data/test/test_exception_handler.rb +56 -0
  69. data/test/test_extensions.rb +127 -0
  70. data/test/test_fetch.rb +50 -0
  71. data/test/test_launcher.rb +85 -0
  72. data/test/test_logging.rb +35 -0
  73. data/test/test_manager.rb +50 -0
  74. data/test/test_middleware.rb +158 -0
  75. data/test/test_processor.rb +201 -0
  76. data/test/test_rails.rb +22 -0
  77. data/test/test_redis_connection.rb +127 -0
  78. data/test/test_retry.rb +326 -0
  79. data/test/test_retry_exhausted.rb +149 -0
  80. data/test/test_scheduled.rb +115 -0
  81. data/test/test_scheduling.rb +50 -0
  82. data/test/test_sidekiq.rb +107 -0
  83. data/test/test_testing.rb +143 -0
  84. data/test/test_testing_fake.rb +357 -0
  85. data/test/test_testing_inline.rb +94 -0
  86. data/test/test_util.rb +13 -0
  87. data/test/test_web.rb +614 -0
  88. data/test/test_web_helpers.rb +54 -0
  89. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  90. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  91. data/web/assets/images/favicon.ico +0 -0
  92. data/web/assets/images/logo.png +0 -0
  93. data/web/assets/images/status-sd8051fd480.png +0 -0
  94. data/web/assets/images/status/active.png +0 -0
  95. data/web/assets/images/status/idle.png +0 -0
  96. data/web/assets/javascripts/application.js +88 -0
  97. data/web/assets/javascripts/dashboard.js +300 -0
  98. data/web/assets/javascripts/locales/README.md +27 -0
  99. data/web/assets/javascripts/locales/jquery.timeago.ar.js +96 -0
  100. data/web/assets/javascripts/locales/jquery.timeago.bg.js +18 -0
  101. data/web/assets/javascripts/locales/jquery.timeago.bs.js +49 -0
  102. data/web/assets/javascripts/locales/jquery.timeago.ca.js +18 -0
  103. data/web/assets/javascripts/locales/jquery.timeago.cs.js +18 -0
  104. data/web/assets/javascripts/locales/jquery.timeago.cy.js +20 -0
  105. data/web/assets/javascripts/locales/jquery.timeago.da.js +18 -0
  106. data/web/assets/javascripts/locales/jquery.timeago.de.js +18 -0
  107. data/web/assets/javascripts/locales/jquery.timeago.el.js +18 -0
  108. data/web/assets/javascripts/locales/jquery.timeago.en-short.js +20 -0
  109. data/web/assets/javascripts/locales/jquery.timeago.en.js +20 -0
  110. data/web/assets/javascripts/locales/jquery.timeago.es.js +18 -0
  111. data/web/assets/javascripts/locales/jquery.timeago.et.js +18 -0
  112. data/web/assets/javascripts/locales/jquery.timeago.fa.js +22 -0
  113. data/web/assets/javascripts/locales/jquery.timeago.fi.js +28 -0
  114. data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +16 -0
  115. data/web/assets/javascripts/locales/jquery.timeago.fr.js +17 -0
  116. data/web/assets/javascripts/locales/jquery.timeago.he.js +18 -0
  117. data/web/assets/javascripts/locales/jquery.timeago.hr.js +49 -0
  118. data/web/assets/javascripts/locales/jquery.timeago.hu.js +18 -0
  119. data/web/assets/javascripts/locales/jquery.timeago.hy.js +18 -0
  120. data/web/assets/javascripts/locales/jquery.timeago.id.js +18 -0
  121. data/web/assets/javascripts/locales/jquery.timeago.it.js +16 -0
  122. data/web/assets/javascripts/locales/jquery.timeago.ja.js +19 -0
  123. data/web/assets/javascripts/locales/jquery.timeago.ko.js +17 -0
  124. data/web/assets/javascripts/locales/jquery.timeago.lt.js +20 -0
  125. data/web/assets/javascripts/locales/jquery.timeago.mk.js +20 -0
  126. data/web/assets/javascripts/locales/jquery.timeago.nl.js +20 -0
  127. data/web/assets/javascripts/locales/jquery.timeago.no.js +18 -0
  128. data/web/assets/javascripts/locales/jquery.timeago.pl.js +31 -0
  129. data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +16 -0
  130. data/web/assets/javascripts/locales/jquery.timeago.pt.js +16 -0
  131. data/web/assets/javascripts/locales/jquery.timeago.ro.js +18 -0
  132. data/web/assets/javascripts/locales/jquery.timeago.rs.js +49 -0
  133. data/web/assets/javascripts/locales/jquery.timeago.ru.js +34 -0
  134. data/web/assets/javascripts/locales/jquery.timeago.sk.js +18 -0
  135. data/web/assets/javascripts/locales/jquery.timeago.sl.js +44 -0
  136. data/web/assets/javascripts/locales/jquery.timeago.sv.js +18 -0
  137. data/web/assets/javascripts/locales/jquery.timeago.th.js +20 -0
  138. data/web/assets/javascripts/locales/jquery.timeago.tr.js +16 -0
  139. data/web/assets/javascripts/locales/jquery.timeago.uk.js +34 -0
  140. data/web/assets/javascripts/locales/jquery.timeago.uz.js +19 -0
  141. data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +20 -0
  142. data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +20 -0
  143. data/web/assets/stylesheets/application.css +754 -0
  144. data/web/assets/stylesheets/bootstrap.css +9 -0
  145. data/web/locales/cs.yml +78 -0
  146. data/web/locales/da.yml +68 -0
  147. data/web/locales/de.yml +69 -0
  148. data/web/locales/el.yml +68 -0
  149. data/web/locales/en.yml +79 -0
  150. data/web/locales/es.yml +69 -0
  151. data/web/locales/fr.yml +78 -0
  152. data/web/locales/hi.yml +75 -0
  153. data/web/locales/it.yml +69 -0
  154. data/web/locales/ja.yml +78 -0
  155. data/web/locales/ko.yml +68 -0
  156. data/web/locales/nb.yml +77 -0
  157. data/web/locales/nl.yml +68 -0
  158. data/web/locales/pl.yml +59 -0
  159. data/web/locales/pt-br.yml +68 -0
  160. data/web/locales/pt.yml +67 -0
  161. data/web/locales/ru.yml +78 -0
  162. data/web/locales/sv.yml +68 -0
  163. data/web/locales/ta.yml +75 -0
  164. data/web/locales/uk.yml +76 -0
  165. data/web/locales/zh-cn.yml +68 -0
  166. data/web/locales/zh-tw.yml +68 -0
  167. data/web/views/_footer.erb +17 -0
  168. data/web/views/_job_info.erb +88 -0
  169. data/web/views/_nav.erb +66 -0
  170. data/web/views/_paging.erb +23 -0
  171. data/web/views/_poll_js.erb +5 -0
  172. data/web/views/_poll_link.erb +7 -0
  173. data/web/views/_status.erb +4 -0
  174. data/web/views/_summary.erb +40 -0
  175. data/web/views/busy.erb +94 -0
  176. data/web/views/dashboard.erb +75 -0
  177. data/web/views/dead.erb +34 -0
  178. data/web/views/layout.erb +32 -0
  179. data/web/views/morgue.erb +71 -0
  180. data/web/views/queue.erb +45 -0
  181. data/web/views/queues.erb +28 -0
  182. data/web/views/retries.erb +74 -0
  183. data/web/views/retry.erb +34 -0
  184. data/web/views/scheduled.erb +54 -0
  185. data/web/views/scheduled_job_info.erb +8 -0
  186. metadata +408 -0
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+ begin
3
+ require 'active_support/core_ext/class/attribute'
4
+ rescue LoadError
5
+
6
+ # A dumbed down version of ActiveSupport's
7
+ # Class#class_attribute helper.
8
+ class Class
9
+ def class_attribute(*attrs)
10
+ instance_writer = true
11
+
12
+ attrs.each do |name|
13
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
14
+ def self.#{name}() nil end
15
+ def self.#{name}?() !!#{name} end
16
+
17
+ def self.#{name}=(val)
18
+ singleton_class.class_eval do
19
+ define_method(:#{name}) { val }
20
+ end
21
+
22
+ if singleton_class?
23
+ class_eval do
24
+ def #{name}
25
+ defined?(@#{name}) ? @#{name} : singleton_class.#{name}
26
+ end
27
+ end
28
+ end
29
+ val
30
+ end
31
+
32
+ def #{name}
33
+ defined?(@#{name}) ? @#{name} : self.class.#{name}
34
+ end
35
+
36
+ def #{name}?
37
+ !!#{name}
38
+ end
39
+ RUBY
40
+
41
+ attr_writer name if instance_writer
42
+ end
43
+ end
44
+
45
+ private
46
+ def singleton_class?
47
+ ancestors.first != self
48
+ end
49
+ end
50
+ end
51
+
52
+ begin
53
+ require 'active_support/core_ext/hash/keys'
54
+ require 'active_support/core_ext/hash/deep_merge'
55
+ rescue LoadError
56
+ class Hash
57
+ def stringify_keys
58
+ keys.each do |key|
59
+ self[key.to_s] = delete(key)
60
+ end
61
+ self
62
+ end if !{}.respond_to?(:stringify_keys)
63
+
64
+ def symbolize_keys
65
+ keys.each do |key|
66
+ self[(key.to_sym rescue key) || key] = delete(key)
67
+ end
68
+ self
69
+ end if !{}.respond_to?(:symbolize_keys)
70
+
71
+ def deep_merge(other_hash, &block)
72
+ dup.deep_merge!(other_hash, &block)
73
+ end if !{}.respond_to?(:deep_merge)
74
+
75
+ def deep_merge!(other_hash, &block)
76
+ other_hash.each_pair do |k,v|
77
+ tv = self[k]
78
+ if tv.is_a?(Hash) && v.is_a?(Hash)
79
+ self[k] = tv.deep_merge(v, &block)
80
+ else
81
+ self[k] = block && tv ? block.call(k, tv, v) : v
82
+ end
83
+ end
84
+ self
85
+ end if !{}.respond_to?(:deep_merge!)
86
+ end
87
+ end
88
+
89
+ begin
90
+ require 'active_support/core_ext/string/inflections'
91
+ rescue LoadError
92
+ class String
93
+ def constantize
94
+ names = self.split('::')
95
+ names.shift if names.empty? || names.first.empty?
96
+
97
+ constant = Object
98
+ names.each do |name|
99
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
100
+ end
101
+ constant
102
+ end
103
+ end if !"".respond_to?(:constantize)
104
+ end
105
+
106
+
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ require 'sidekiq'
3
+
4
+ module Sidekiq
5
+ module ExceptionHandler
6
+
7
+ class Logger
8
+ def call(ex, ctxHash)
9
+ Sidekiq.logger.warn(Sidekiq.dump_json(ctxHash)) if !ctxHash.empty?
10
+ Sidekiq.logger.warn "#{ex.class.name}: #{ex.message}"
11
+ Sidekiq.logger.warn ex.backtrace.join("\n") unless ex.backtrace.nil?
12
+ end
13
+
14
+ # Set up default handler which just logs the error
15
+ Sidekiq.error_handlers << Sidekiq::ExceptionHandler::Logger.new
16
+ end
17
+
18
+ def handle_exception(ex, ctxHash={})
19
+ Sidekiq.error_handlers.each do |handler|
20
+ begin
21
+ handler.call(ex, ctxHash)
22
+ rescue => ex
23
+ Sidekiq.logger.error "!!! ERROR HANDLER THREW AN ERROR !!!"
24
+ Sidekiq.logger.error ex
25
+ Sidekiq.logger.error ex.backtrace.join("\n") unless ex.backtrace.nil?
26
+ end
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ require 'sidekiq/extensions/generic_proxy'
3
+
4
+ module Sidekiq
5
+ module Extensions
6
+ ##
7
+ # Adds 'delay', 'delay_for' and `delay_until` methods to ActionMailer to offload arbitrary email
8
+ # delivery to Sidekiq. Example:
9
+ #
10
+ # UserMailer.delay.send_welcome_email(new_user)
11
+ # UserMailer.delay_for(5.days).send_welcome_email(new_user)
12
+ # UserMailer.delay_until(5.days.from_now).send_welcome_email(new_user)
13
+ class DelayedMailer
14
+ include Sidekiq::Worker
15
+
16
+ def perform(yml)
17
+ (target, method_name, args) = YAML.load(yml)
18
+ msg = target.public_send(method_name, *args)
19
+ # The email method can return nil, which causes ActionMailer to return
20
+ # an undeliverable empty message.
21
+ if msg
22
+ deliver(msg)
23
+ else
24
+ raise "#{target.name}##{method_name} returned an undeliverable mail object"
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def deliver(msg)
31
+ if msg.respond_to?(:deliver_now)
32
+ # Rails 4.2/5.0
33
+ msg.deliver_now
34
+ else
35
+ # Rails 3.2/4.0/4.1
36
+ msg.deliver
37
+ end
38
+ end
39
+ end
40
+
41
+ module ActionMailer
42
+ def sidekiq_delay(options={})
43
+ Proxy.new(DelayedMailer, self, options)
44
+ end
45
+ def sidekiq_delay_for(interval, options={})
46
+ Proxy.new(DelayedMailer, self, options.merge('at' => Time.now.to_f + interval.to_f))
47
+ end
48
+ def sidekiq_delay_until(timestamp, options={})
49
+ Proxy.new(DelayedMailer, self, options.merge('at' => timestamp.to_f))
50
+ end
51
+ alias_method :delay, :sidekiq_delay
52
+ alias_method :delay_for, :sidekiq_delay_for
53
+ alias_method :delay_until, :sidekiq_delay_until
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ require 'sidekiq/extensions/generic_proxy'
3
+
4
+ module Sidekiq
5
+ module Extensions
6
+ ##
7
+ # Adds 'delay', 'delay_for' and `delay_until` methods to ActiveRecord to offload instance method
8
+ # execution to Sidekiq. Examples:
9
+ #
10
+ # User.recent_signups.each { |user| user.delay.mark_as_awesome }
11
+ #
12
+ # Please note, this is not recommended as this will serialize the entire
13
+ # object to Redis. Your Sidekiq jobs should pass IDs, not entire instances.
14
+ # This is here for backwards compatibility with Delayed::Job only.
15
+ class DelayedModel
16
+ include Sidekiq::Worker
17
+
18
+ def perform(yml)
19
+ (target, method_name, args) = YAML.load(yml)
20
+ target.__send__(method_name, *args)
21
+ end
22
+ end
23
+
24
+ module ActiveRecord
25
+ def sidekiq_delay(options={})
26
+ Proxy.new(DelayedModel, self, options)
27
+ end
28
+ def sidekiq_delay_for(interval, options={})
29
+ Proxy.new(DelayedModel, self, options.merge('at' => Time.now.to_f + interval.to_f))
30
+ end
31
+ def sidekiq_delay_until(timestamp, options={})
32
+ Proxy.new(DelayedModel, self, options.merge('at' => timestamp.to_f))
33
+ end
34
+ alias_method :delay, :sidekiq_delay
35
+ alias_method :delay_for, :sidekiq_delay_for
36
+ alias_method :delay_until, :sidekiq_delay_until
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ require 'sidekiq/extensions/generic_proxy'
3
+
4
+ module Sidekiq
5
+ module Extensions
6
+ ##
7
+ # Adds 'delay', 'delay_for' and `delay_until` methods to all Classes to offload class method
8
+ # execution to Sidekiq. Examples:
9
+ #
10
+ # User.delay.delete_inactive
11
+ # Wikipedia.delay.download_changes_for(Date.today)
12
+ #
13
+ class DelayedClass
14
+ include Sidekiq::Worker
15
+
16
+ def perform(yml)
17
+ (target, method_name, args) = YAML.load(yml)
18
+ target.__send__(method_name, *args)
19
+ end
20
+ end
21
+
22
+ module Klass
23
+ def sidekiq_delay(options={})
24
+ Proxy.new(DelayedClass, self, options)
25
+ end
26
+ def sidekiq_delay_for(interval, options={})
27
+ Proxy.new(DelayedClass, self, options.merge('at' => Time.now.to_f + interval.to_f))
28
+ end
29
+ def sidekiq_delay_until(timestamp, options={})
30
+ Proxy.new(DelayedClass, self, options.merge('at' => timestamp.to_f))
31
+ end
32
+ alias_method :delay, :sidekiq_delay
33
+ alias_method :delay_for, :sidekiq_delay_for
34
+ alias_method :delay_until, :sidekiq_delay_until
35
+ end
36
+
37
+ end
38
+ end
39
+
40
+ Module.__send__(:include, Sidekiq::Extensions::Klass) unless defined?(::Rails)
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ require 'yaml'
3
+
4
+ module Sidekiq
5
+ module Extensions
6
+ class Proxy < BasicObject
7
+ def initialize(performable, target, options={})
8
+ @performable = performable
9
+ @target = target
10
+ @opts = options
11
+ end
12
+
13
+ def method_missing(name, *args)
14
+ # Sidekiq has a limitation in that its message must be JSON.
15
+ # JSON can't round trip real Ruby objects so we use YAML to
16
+ # serialize the objects to a String. The YAML will be converted
17
+ # to JSON and then deserialized on the other side back into a
18
+ # Ruby object.
19
+ obj = [@target, name, args]
20
+ @performable.client_push({ 'class' => @performable, 'args' => [::YAML.dump(obj)] }.merge(@opts))
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+ require 'sidekiq'
3
+
4
+ module Sidekiq
5
+ class BasicFetch
6
+ # We want the fetch operation to timeout every few seconds so the thread
7
+ # can check if the process is shutting down.
8
+ TIMEOUT = 2
9
+
10
+ UnitOfWork = Struct.new(:queue, :job) do
11
+ def acknowledge
12
+ # nothing to do
13
+ end
14
+
15
+ def queue_name
16
+ queue.sub(/.*queue:/, ''.freeze)
17
+ end
18
+
19
+ def requeue
20
+ Sidekiq.redis do |conn|
21
+ conn.rpush("queue:#{queue_name}", job)
22
+ end
23
+ end
24
+ end
25
+
26
+ def initialize(options)
27
+ @strictly_ordered_queues = !!options[:strict]
28
+ @queues = options[:queues].map { |q| "queue:#{q}" }
29
+ if @strictly_ordered_queues
30
+ @queues = @queues.uniq
31
+ @queues << TIMEOUT
32
+ end
33
+ end
34
+
35
+ def retrieve_work
36
+ work = Sidekiq.redis { |conn| conn.brpop(*queues_cmd) }
37
+ UnitOfWork.new(*work) if work
38
+ end
39
+
40
+ # Creating the Redis#brpop command takes into account any
41
+ # configured queue weights. By default Redis#brpop returns
42
+ # data from the first queue that has pending elements. We
43
+ # recreate the queue command each time we invoke Redis#brpop
44
+ # to honor weights and avoid queue starvation.
45
+ def queues_cmd
46
+ if @strictly_ordered_queues
47
+ @queues
48
+ else
49
+ queues = @queues.shuffle.uniq
50
+ queues << TIMEOUT
51
+ queues
52
+ end
53
+ end
54
+
55
+
56
+ # By leaving this as a class method, it can be pluggable and used by the Manager actor. Making it
57
+ # an instance method will make it async to the Fetcher actor
58
+ def self.bulk_requeue(inprogress, options)
59
+ return if inprogress.empty?
60
+
61
+ Sidekiq.logger.debug { "Re-queueing terminated jobs" }
62
+ jobs_to_requeue = {}
63
+ inprogress.each do |unit_of_work|
64
+ jobs_to_requeue[unit_of_work.queue_name] ||= []
65
+ jobs_to_requeue[unit_of_work.queue_name] << unit_of_work.job
66
+ end
67
+
68
+ Sidekiq.redis do |conn|
69
+ conn.pipelined do
70
+ jobs_to_requeue.each do |queue, jobs|
71
+ conn.rpush("queue:#{queue}", jobs)
72
+ end
73
+ end
74
+ end
75
+ Sidekiq.logger.info("Pushed #{inprogress.size} jobs back to Redis")
76
+ rescue => ex
77
+ Sidekiq.logger.warn("Failed to requeue #{inprogress.size} jobs: #{ex.message}")
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+ require 'sidekiq/manager'
4
+ require 'sidekiq/fetch'
5
+ require 'sidekiq/scheduled'
6
+
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.
12
+ class Launcher
13
+ include Util
14
+
15
+ attr_accessor :manager, :poller, :fetcher
16
+
17
+ def initialize(options)
18
+ @manager = Sidekiq::Manager.new(options)
19
+ @poller = Sidekiq::Scheduled::Poller.new
20
+ @done = false
21
+ @options = options
22
+ end
23
+
24
+ def run
25
+ @thread = safe_thread("heartbeat", &method(:start_heartbeat))
26
+ @poller.start
27
+ @manager.start
28
+ end
29
+
30
+ # Stops this instance from processing any more jobs,
31
+ #
32
+ def quiet
33
+ @done = true
34
+ @manager.quiet
35
+ @poller.terminate
36
+ end
37
+
38
+ # Shuts down the process. This method does not
39
+ # return until all work is complete and cleaned up.
40
+ # It can take up to the timeout to complete.
41
+ def stop
42
+ deadline = Time.now + @options[:timeout]
43
+
44
+ @done = true
45
+ @manager.quiet
46
+ @poller.terminate
47
+
48
+ @manager.stop(deadline)
49
+
50
+ # Requeue everything in case there was a worker who grabbed work while stopped
51
+ # This call is a no-op in Sidekiq but necessary for Sidekiq Pro.
52
+ strategy = (@options[:fetch] || Sidekiq::BasicFetch)
53
+ strategy.bulk_requeue([], @options)
54
+
55
+ clear_heartbeat
56
+ end
57
+
58
+ def stopping?
59
+ @done
60
+ end
61
+
62
+ private unless $TESTING
63
+
64
+ JVM_RESERVED_SIGNALS = ['USR1', 'USR2'] # Don't Process#kill if we get these signals via the API
65
+
66
+ def heartbeat(k, data, json)
67
+ results = Sidekiq::CLI::PROCTITLES.map {|x| x.(self, data) }
68
+ results.compact!
69
+ $0 = results.join(' ')
70
+
71
+ ❤(k, json)
72
+ end
73
+
74
+ def ❤(key, json)
75
+ fails = procd = 0
76
+ begin
77
+ Processor::FAILURE.update {|curr| fails = curr; 0 }
78
+ Processor::PROCESSED.update {|curr| procd = curr; 0 }
79
+
80
+ workers_key = "#{key}:workers".freeze
81
+ nowdate = Time.now.utc.strftime("%Y-%m-%d".freeze)
82
+ Sidekiq.redis do |conn|
83
+ conn.pipelined do
84
+ conn.incrby("stat:processed".freeze, procd)
85
+ conn.incrby("stat:processed:#{nowdate}", procd)
86
+ conn.incrby("stat:failed".freeze, fails)
87
+ conn.incrby("stat:failed:#{nowdate}", fails)
88
+ conn.del(workers_key)
89
+ Processor::WORKER_STATE.each_pair do |tid, hash|
90
+ conn.hset(workers_key, tid, Sidekiq.dump_json(hash))
91
+ end
92
+ conn.expire(workers_key, 60)
93
+ end
94
+ end
95
+ fails = procd = 0
96
+
97
+ _, _, _, msg = Sidekiq.redis do |conn|
98
+ conn.pipelined do
99
+ conn.sadd('processes', key)
100
+ conn.hmset(key, 'info', json, 'busy', Processor::WORKER_STATE.size, 'beat', Time.now.to_f, 'quiet', @done)
101
+ conn.expire(key, 60)
102
+ conn.rpop("#{key}-signals")
103
+ end
104
+ end
105
+
106
+ return unless msg
107
+
108
+ if JVM_RESERVED_SIGNALS.include?(msg)
109
+ Sidekiq::CLI.instance.handle_signal(msg)
110
+ else
111
+ ::Process.kill(msg, $$)
112
+ end
113
+ rescue => e
114
+ # ignore all redis/network issues
115
+ logger.error("heartbeat: #{e.message}")
116
+ # don't lose the counts if there was a network issue
117
+ Processor::PROCESSED.increment(procd)
118
+ Processor::FAILURE.increment(fails)
119
+ end
120
+ end
121
+
122
+ def start_heartbeat
123
+ k = identity
124
+ data = {
125
+ 'hostname' => hostname,
126
+ 'started_at' => Time.now.to_f,
127
+ 'pid' => $$,
128
+ 'tag' => @options[:tag] || '',
129
+ 'concurrency' => @options[:concurrency],
130
+ 'queues' => @options[:queues].uniq,
131
+ 'labels' => @options[:labels],
132
+ 'identity' => k,
133
+ }
134
+ # this data doesn't change so dump it to a string
135
+ # now so we don't need to dump it every heartbeat.
136
+ json = Sidekiq.dump_json(data)
137
+
138
+ while true
139
+ heartbeat(k, data, json)
140
+ sleep 5
141
+ end
142
+ Sidekiq.logger.info("Heartbeat stopping...")
143
+ end
144
+
145
+ def clear_heartbeat
146
+ # Remove record from Redis since we are shutting down.
147
+ # Note we don't stop the heartbeat thread; if the process
148
+ # doesn't actually exit, it'll reappear in the Web UI.
149
+ Sidekiq.redis do |conn|
150
+ conn.pipelined do
151
+ conn.srem('processes', identity)
152
+ conn.del("#{identity}:workers")
153
+ end
154
+ end
155
+ rescue
156
+ # best effort, ignore network errors
157
+ end
158
+
159
+ end
160
+ end