mini-clean-lib 0.0.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 (151) hide show
  1. checksums.yaml +7 -0
  2. data/mini-clean-lib.gemspec +12 -0
  3. data/sidekiq-8.1.6/Changes.md +2515 -0
  4. data/sidekiq-8.1.6/LICENSE.txt +9 -0
  5. data/sidekiq-8.1.6/README.md +124 -0
  6. data/sidekiq-8.1.6/bin/kiq +17 -0
  7. data/sidekiq-8.1.6/bin/lint-herb +13 -0
  8. data/sidekiq-8.1.6/bin/multi_queue_bench +271 -0
  9. data/sidekiq-8.1.6/bin/sidekiq +37 -0
  10. data/sidekiq-8.1.6/bin/sidekiqload +256 -0
  11. data/sidekiq-8.1.6/bin/sidekiqmon +11 -0
  12. data/sidekiq-8.1.6/bin/webload +69 -0
  13. data/sidekiq-8.1.6/lib/active_job/queue_adapters/sidekiq_adapter.rb +124 -0
  14. data/sidekiq-8.1.6/lib/generators/sidekiq/job_generator.rb +71 -0
  15. data/sidekiq-8.1.6/lib/generators/sidekiq/templates/job.rb.erb +9 -0
  16. data/sidekiq-8.1.6/lib/generators/sidekiq/templates/job_spec.rb.erb +6 -0
  17. data/sidekiq-8.1.6/lib/generators/sidekiq/templates/job_test.rb.erb +8 -0
  18. data/sidekiq-8.1.6/lib/sidekiq/api.rb +1466 -0
  19. data/sidekiq-8.1.6/lib/sidekiq/capsule.rb +135 -0
  20. data/sidekiq-8.1.6/lib/sidekiq/cli.rb +433 -0
  21. data/sidekiq-8.1.6/lib/sidekiq/client.rb +306 -0
  22. data/sidekiq-8.1.6/lib/sidekiq/component.rb +132 -0
  23. data/sidekiq-8.1.6/lib/sidekiq/config.rb +316 -0
  24. data/sidekiq-8.1.6/lib/sidekiq/deploy.rb +64 -0
  25. data/sidekiq-8.1.6/lib/sidekiq/embedded.rb +64 -0
  26. data/sidekiq-8.1.6/lib/sidekiq/fetch.rb +89 -0
  27. data/sidekiq-8.1.6/lib/sidekiq/iterable_job.rb +56 -0
  28. data/sidekiq-8.1.6/lib/sidekiq/job/interrupt_handler.rb +24 -0
  29. data/sidekiq-8.1.6/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  30. data/sidekiq-8.1.6/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  31. data/sidekiq-8.1.6/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  32. data/sidekiq-8.1.6/lib/sidekiq/job/iterable.rb +322 -0
  33. data/sidekiq-8.1.6/lib/sidekiq/job.rb +400 -0
  34. data/sidekiq-8.1.6/lib/sidekiq/job_logger.rb +54 -0
  35. data/sidekiq-8.1.6/lib/sidekiq/job_retry.rb +332 -0
  36. data/sidekiq-8.1.6/lib/sidekiq/job_util.rb +113 -0
  37. data/sidekiq-8.1.6/lib/sidekiq/launcher.rb +283 -0
  38. data/sidekiq-8.1.6/lib/sidekiq/loader.rb +57 -0
  39. data/sidekiq-8.1.6/lib/sidekiq/logger.rb +87 -0
  40. data/sidekiq-8.1.6/lib/sidekiq/manager.rb +134 -0
  41. data/sidekiq-8.1.6/lib/sidekiq/metrics/query.rb +184 -0
  42. data/sidekiq-8.1.6/lib/sidekiq/metrics/shared.rb +109 -0
  43. data/sidekiq-8.1.6/lib/sidekiq/metrics/tracking.rb +153 -0
  44. data/sidekiq-8.1.6/lib/sidekiq/middleware/chain.rb +207 -0
  45. data/sidekiq-8.1.6/lib/sidekiq/middleware/current_attributes.rb +120 -0
  46. data/sidekiq-8.1.6/lib/sidekiq/middleware/i18n.rb +44 -0
  47. data/sidekiq-8.1.6/lib/sidekiq/middleware/modules.rb +23 -0
  48. data/sidekiq-8.1.6/lib/sidekiq/monitor.rb +143 -0
  49. data/sidekiq-8.1.6/lib/sidekiq/paginator.rb +74 -0
  50. data/sidekiq-8.1.6/lib/sidekiq/processor.rb +298 -0
  51. data/sidekiq-8.1.6/lib/sidekiq/profiler.rb +73 -0
  52. data/sidekiq-8.1.6/lib/sidekiq/rails.rb +63 -0
  53. data/sidekiq-8.1.6/lib/sidekiq/redis_client_adapter.rb +117 -0
  54. data/sidekiq-8.1.6/lib/sidekiq/redis_connection.rb +115 -0
  55. data/sidekiq-8.1.6/lib/sidekiq/ring_buffer.rb +32 -0
  56. data/sidekiq-8.1.6/lib/sidekiq/scheduled.rb +235 -0
  57. data/sidekiq-8.1.6/lib/sidekiq/sd_notify.rb +149 -0
  58. data/sidekiq-8.1.6/lib/sidekiq/systemd.rb +26 -0
  59. data/sidekiq-8.1.6/lib/sidekiq/test_api.rb +331 -0
  60. data/sidekiq-8.1.6/lib/sidekiq/testing/inline.rb +2 -0
  61. data/sidekiq-8.1.6/lib/sidekiq/testing.rb +3 -0
  62. data/sidekiq-8.1.6/lib/sidekiq/transaction_aware_client.rb +59 -0
  63. data/sidekiq-8.1.6/lib/sidekiq/tui/controls.rb +53 -0
  64. data/sidekiq-8.1.6/lib/sidekiq/tui/filtering.rb +53 -0
  65. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/base_tab.rb +204 -0
  66. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/busy.rb +118 -0
  67. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/dead.rb +19 -0
  68. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/home.rb +144 -0
  69. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/metrics.rb +131 -0
  70. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/queues.rb +95 -0
  71. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/retries.rb +19 -0
  72. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/scheduled.rb +19 -0
  73. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/set_tab.rb +96 -0
  74. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs.rb +15 -0
  75. data/sidekiq-8.1.6/lib/sidekiq/tui.rb +382 -0
  76. data/sidekiq-8.1.6/lib/sidekiq/version.rb +10 -0
  77. data/sidekiq-8.1.6/lib/sidekiq/web/action.rb +178 -0
  78. data/sidekiq-8.1.6/lib/sidekiq/web/application.rb +480 -0
  79. data/sidekiq-8.1.6/lib/sidekiq/web/config.rb +117 -0
  80. data/sidekiq-8.1.6/lib/sidekiq/web/helpers.rb +474 -0
  81. data/sidekiq-8.1.6/lib/sidekiq/web/router.rb +91 -0
  82. data/sidekiq-8.1.6/lib/sidekiq/web.rb +140 -0
  83. data/sidekiq-8.1.6/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  84. data/sidekiq-8.1.6/lib/sidekiq.rb +161 -0
  85. data/sidekiq-8.1.6/sidekiq.gemspec +31 -0
  86. data/sidekiq-8.1.6/web/assets/images/apple-touch-icon.png +0 -0
  87. data/sidekiq-8.1.6/web/assets/images/favicon.ico +0 -0
  88. data/sidekiq-8.1.6/web/assets/images/logo.png +0 -0
  89. data/sidekiq-8.1.6/web/assets/images/status.png +0 -0
  90. data/sidekiq-8.1.6/web/assets/javascripts/application.js +204 -0
  91. data/sidekiq-8.1.6/web/assets/javascripts/base-charts.js +120 -0
  92. data/sidekiq-8.1.6/web/assets/javascripts/chart.min.js +13 -0
  93. data/sidekiq-8.1.6/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  94. data/sidekiq-8.1.6/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  95. data/sidekiq-8.1.6/web/assets/javascripts/dashboard-charts.js +194 -0
  96. data/sidekiq-8.1.6/web/assets/javascripts/dashboard.js +63 -0
  97. data/sidekiq-8.1.6/web/assets/javascripts/metrics.js +280 -0
  98. data/sidekiq-8.1.6/web/assets/stylesheets/style.css +776 -0
  99. data/sidekiq-8.1.6/web/locales/ar.yml +89 -0
  100. data/sidekiq-8.1.6/web/locales/cs.yml +80 -0
  101. data/sidekiq-8.1.6/web/locales/da.yml +77 -0
  102. data/sidekiq-8.1.6/web/locales/de.yml +83 -0
  103. data/sidekiq-8.1.6/web/locales/el.yml +89 -0
  104. data/sidekiq-8.1.6/web/locales/en.yml +110 -0
  105. data/sidekiq-8.1.6/web/locales/es.yml +109 -0
  106. data/sidekiq-8.1.6/web/locales/fa.yml +82 -0
  107. data/sidekiq-8.1.6/web/locales/fr.yml +100 -0
  108. data/sidekiq-8.1.6/web/locales/gd.yml +110 -0
  109. data/sidekiq-8.1.6/web/locales/he.yml +82 -0
  110. data/sidekiq-8.1.6/web/locales/hi.yml +77 -0
  111. data/sidekiq-8.1.6/web/locales/it.yml +109 -0
  112. data/sidekiq-8.1.6/web/locales/ja.yml +92 -0
  113. data/sidekiq-8.1.6/web/locales/ko.yml +70 -0
  114. data/sidekiq-8.1.6/web/locales/lt.yml +85 -0
  115. data/sidekiq-8.1.6/web/locales/nb.yml +79 -0
  116. data/sidekiq-8.1.6/web/locales/nl.yml +70 -0
  117. data/sidekiq-8.1.6/web/locales/pl.yml +61 -0
  118. data/sidekiq-8.1.6/web/locales/pt-BR.yml +97 -0
  119. data/sidekiq-8.1.6/web/locales/pt.yml +69 -0
  120. data/sidekiq-8.1.6/web/locales/ru.yml +85 -0
  121. data/sidekiq-8.1.6/web/locales/sv.yml +70 -0
  122. data/sidekiq-8.1.6/web/locales/ta.yml +77 -0
  123. data/sidekiq-8.1.6/web/locales/tr.yml +102 -0
  124. data/sidekiq-8.1.6/web/locales/uk.yml +102 -0
  125. data/sidekiq-8.1.6/web/locales/ur.yml +82 -0
  126. data/sidekiq-8.1.6/web/locales/vi.yml +85 -0
  127. data/sidekiq-8.1.6/web/locales/zh-CN.yml +107 -0
  128. data/sidekiq-8.1.6/web/locales/zh-TW.yml +103 -0
  129. data/sidekiq-8.1.6/web/views/_footer.html.erb +32 -0
  130. data/sidekiq-8.1.6/web/views/_job_info.html.erb +115 -0
  131. data/sidekiq-8.1.6/web/views/_metrics_period_select.html.erb +15 -0
  132. data/sidekiq-8.1.6/web/views/_nav.html.erb +45 -0
  133. data/sidekiq-8.1.6/web/views/_paging.html.erb +26 -0
  134. data/sidekiq-8.1.6/web/views/_poll_link.html.erb +4 -0
  135. data/sidekiq-8.1.6/web/views/_summary.html.erb +40 -0
  136. data/sidekiq-8.1.6/web/views/busy.html.erb +155 -0
  137. data/sidekiq-8.1.6/web/views/dashboard.html.erb +104 -0
  138. data/sidekiq-8.1.6/web/views/dead.html.erb +38 -0
  139. data/sidekiq-8.1.6/web/views/filtering.html.erb +6 -0
  140. data/sidekiq-8.1.6/web/views/layout.html.erb +26 -0
  141. data/sidekiq-8.1.6/web/views/metrics.html.erb +85 -0
  142. data/sidekiq-8.1.6/web/views/metrics_for_job.html.erb +58 -0
  143. data/sidekiq-8.1.6/web/views/morgue.html.erb +69 -0
  144. data/sidekiq-8.1.6/web/views/profiles.html.erb +43 -0
  145. data/sidekiq-8.1.6/web/views/queue.html.erb +57 -0
  146. data/sidekiq-8.1.6/web/views/queues.html.erb +46 -0
  147. data/sidekiq-8.1.6/web/views/retries.html.erb +77 -0
  148. data/sidekiq-8.1.6/web/views/retry.html.erb +39 -0
  149. data/sidekiq-8.1.6/web/views/scheduled.html.erb +64 -0
  150. data/sidekiq-8.1.6/web/views/scheduled_job_info.html.erb +8 -0
  151. metadata +190 -0
@@ -0,0 +1,283 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/manager"
4
+ require "sidekiq/capsule"
5
+ require "sidekiq/scheduled"
6
+ require "sidekiq/ring_buffer"
7
+
8
+ module Sidekiq
9
+ # The Launcher starts the Capsule Managers, the Poller thread and provides the process heartbeat.
10
+ class Launcher
11
+ include Sidekiq::Component
12
+
13
+ STATS_TTL = 5 * 365 * 24 * 60 * 60 # 5 years
14
+
15
+ PROCTITLES = [
16
+ proc { "sidekiq" },
17
+ proc { Sidekiq::VERSION },
18
+ proc { |me, data| data["tag"] },
19
+ proc { |me, data| "[#{Processor::WORK_STATE.size} of #{me.config.total_concurrency} busy]" },
20
+ proc { |me, data| "stopping" if me.stopping? }
21
+ ]
22
+
23
+ attr_accessor :managers, :poller
24
+
25
+ def initialize(config, embedded: false)
26
+ @config = config
27
+ @embedded = embedded
28
+ @managers = config.capsules.values.map do |cap|
29
+ Sidekiq::Manager.new(cap)
30
+ end
31
+ @poller = Sidekiq::Scheduled::Poller.new(@config)
32
+ @done = false
33
+ end
34
+
35
+ # Start this Sidekiq instance. If an embedding process already
36
+ # has a heartbeat thread, caller can use `async_beat: false`
37
+ # and instead have thread call Launcher#heartbeat every N seconds.
38
+ def run(async_beat: true)
39
+ logger.debug { @config.merge!({}) }
40
+ Sidekiq.freeze!
41
+ @thread = safe_thread("heartbeat", &method(:start_heartbeat)) if async_beat
42
+ @poller.start
43
+ @managers.each(&:start)
44
+ end
45
+
46
+ # Stops this instance from processing any more jobs,
47
+ def quiet
48
+ return if @done
49
+
50
+ @done = true
51
+ @managers.each(&:quiet)
52
+ @poller.terminate
53
+ fire_event(:quiet, reverse: true)
54
+ end
55
+
56
+ # Shuts down this Sidekiq instance. Waits up to the deadline for all jobs to complete.
57
+ def stop
58
+ deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @config[:timeout]
59
+
60
+ quiet
61
+ stoppers = @managers.map do |mgr|
62
+ Thread.new do
63
+ mgr.stop(deadline)
64
+ end
65
+ end
66
+
67
+ fire_event(:shutdown, reverse: true)
68
+ stoppers.each(&:join)
69
+
70
+ clear_heartbeat
71
+ fire_event(:exit, reverse: true)
72
+ end
73
+
74
+ def stopping?
75
+ @done
76
+ end
77
+
78
+ # If embedding Sidekiq, you can have the process heartbeat
79
+ # call this method to regularly heartbeat rather than creating
80
+ # a separate thread.
81
+ def heartbeat
82
+
83
+ end
84
+
85
+ private
86
+
87
+ BEAT_PAUSE = 10
88
+
89
+ def start_heartbeat
90
+ loop do
91
+ beat
92
+ sleep BEAT_PAUSE
93
+ end
94
+ logger.info("Heartbeat stopping...")
95
+ end
96
+
97
+ def beat
98
+ $0 = PROCTITLES.map { |proc| proc.call(self, to_data) }.compact.join(" ") unless @embedded
99
+
100
+ end
101
+
102
+ def clear_heartbeat
103
+ flush_stats
104
+
105
+ # Remove record from Redis since we are shutting down.
106
+ # Note we don't stop the heartbeat thread; if the process
107
+ # doesn't actually exit, it'll reappear in the Web UI.
108
+ redis do |conn|
109
+ conn.pipelined do |pipeline|
110
+ pipeline.srem("processes", [identity])
111
+ pipeline.unlink("#{identity}:work")
112
+ end
113
+ end
114
+ rescue
115
+ # best effort, ignore network errors
116
+ end
117
+
118
+ def flush_stats
119
+ fails = Processor::FAILURE.reset
120
+ procd = Processor::PROCESSED.reset
121
+ return if fails + procd == 0
122
+
123
+ nowdate = Time.now.utc.strftime("%Y-%m-%d")
124
+ begin
125
+ redis do |conn|
126
+ conn.pipelined do |pipeline|
127
+ pipeline.incrby("stat:processed", procd)
128
+ pipeline.incrby("stat:processed:#{nowdate}", procd)
129
+ pipeline.expire("stat:processed:#{nowdate}", STATS_TTL)
130
+
131
+ pipeline.incrby("stat:failed", fails)
132
+ pipeline.incrby("stat:failed:#{nowdate}", fails)
133
+ pipeline.expire("stat:failed:#{nowdate}", STATS_TTL)
134
+ end
135
+ end
136
+ rescue => ex
137
+ logger.warn("Unable to flush stats: #{ex}")
138
+ end
139
+ end
140
+
141
+ def ❤
142
+ key = identity
143
+ fails = procd = 0
144
+
145
+ idle_timeout = config[:redis_idle_timeout]
146
+ if idle_timeout
147
+ config.capsules.each_value { |cap| cap.local_redis_pool.reap(idle_seconds: idle_timeout, &:close) }
148
+ config.local_redis_pool.reap(idle_seconds: idle_timeout, &:close)
149
+ end
150
+
151
+ begin
152
+ flush_stats
153
+
154
+ curstate = Processor::WORK_STATE.dup
155
+ curstate.transform_values! { |val| Sidekiq.dump_json(val) }
156
+
157
+ redis do |conn|
158
+ # work is the current set of executing jobs
159
+ work_key = "#{key}:work"
160
+ conn.multi do |transaction|
161
+ transaction.unlink(work_key)
162
+ if curstate.size > 0
163
+ transaction.hset(work_key, curstate)
164
+ transaction.expire(work_key, 60)
165
+ end
166
+ end
167
+ end
168
+
169
+ rtt = check_rtt
170
+
171
+ fails = procd = 0
172
+ kb = memory_usage(::Process.pid)
173
+
174
+ _, exists, _, _, signal = redis { |conn|
175
+ conn.multi { |transaction|
176
+ transaction.sadd("processes", [key])
177
+ transaction.exists(key)
178
+ transaction.hset(key, "info", to_json,
179
+ "concurrency", @config.total_concurrency,
180
+ "busy", curstate.size,
181
+ "beat", Time.now.to_f,
182
+ "rtt_us", rtt,
183
+ "quiet", @done.to_s,
184
+ "rss", kb)
185
+ transaction.expire(key, 60)
186
+ transaction.rpop("#{key}-signals")
187
+ }
188
+ }
189
+
190
+ # first heartbeat or recovering from an outage and need to reestablish our heartbeat
191
+ fire_event(:heartbeat) unless exists > 0
192
+ fire_event(:beat, oneshot: false)
193
+
194
+ ::Process.kill(signal, ::Process.pid) if signal && !@embedded
195
+ rescue => e
196
+ # ignore all redis/network issues
197
+ logger.error("heartbeat: #{e}")
198
+ # don't lose the counts if there was a network issue
199
+ Processor::PROCESSED.incr(procd)
200
+ Processor::FAILURE.incr(fails)
201
+ end
202
+ end
203
+
204
+ # We run the heartbeat every five seconds.
205
+ # Capture five samples of RTT, log a warning if each sample
206
+ # is above our warning threshold.
207
+ RTT_READINGS = RingBuffer.new(5)
208
+ RTT_WARNING_LEVEL = 50_000
209
+
210
+ def check_rtt
211
+ a = b = 0
212
+ redis do |x|
213
+ a = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
214
+ x.ping
215
+ b = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
216
+ end
217
+ rtt = b - a
218
+ RTT_READINGS << rtt
219
+ # Ideal RTT for Redis is < 1000µs
220
+ # Workable is < 10,000µs
221
+ # Log a warning if it's a disaster.
222
+ if RTT_READINGS.all? { |x| x > RTT_WARNING_LEVEL }
223
+ logger.warn <<~EOM
224
+ Your Redis network connection appears to be performing poorly.
225
+ Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
226
+ Ensure Redis is running in the same AZ or datacenter as Sidekiq and that
227
+ your Sidekiq process is not CPU-saturated; reduce your concurrency and/or
228
+ see https://github.com/sidekiq/sidekiq/discussions/5039
229
+ EOM
230
+ RTT_READINGS.reset
231
+ end
232
+ rtt
233
+ end
234
+
235
+ MEMORY_GRABBER = case RUBY_PLATFORM
236
+ when /linux/
237
+ ->(pid) {
238
+ IO.readlines("/proc/#{$$}/status").each do |line|
239
+ next unless line.start_with?("VmRSS:")
240
+ break line.split[1].to_i
241
+ end
242
+ }
243
+ when /darwin|bsd/
244
+ ->(pid) {
245
+ `ps -o pid,rss -p #{pid}`.lines.last.split.last.to_i
246
+ }
247
+ else
248
+ ->(pid) { 0 }
249
+ end
250
+
251
+ def memory_usage(pid)
252
+ MEMORY_GRABBER.call(pid)
253
+ end
254
+
255
+ def to_data
256
+ @data ||= {
257
+ "hostname" => hostname,
258
+ "started_at" => Time.now.to_f,
259
+ "pid" => ::Process.pid,
260
+ "tag" => @config[:tag] || "",
261
+ "capsules" => @config.capsules.each_with_object({}) { |(name, cap), memo|
262
+ memo[name] = cap.to_h
263
+ },
264
+ #####
265
+ # TODO deprecated, remove in 9.0
266
+ # This data is now found in the `capsules` element above
267
+ "queues" => @config.capsules.values.flat_map { |cap| cap.queues }.uniq,
268
+ "weights" => @config.capsules.values.map(&:weights),
269
+ #####
270
+ "labels" => @config[:labels].to_a,
271
+ "identity" => identity,
272
+ "version" => Sidekiq::VERSION,
273
+ "embedded" => @embedded
274
+ }
275
+ end
276
+
277
+ def to_json
278
+ # this data changes infrequently so dump it to a string
279
+ # now so we don't need to dump it every heartbeat.
280
+ @json ||= Sidekiq.dump_json(to_data)
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,57 @@
1
+ module Sidekiq
2
+ require "sidekiq/component"
3
+
4
+ class Loader
5
+ include Sidekiq::Component
6
+
7
+ def initialize(cfg = Sidekiq.default_configuration)
8
+ @config = cfg
9
+ @load_hooks = Hash.new { |h, k| h[k] = [] }
10
+ @loaded = Set.new
11
+ @lock = Mutex.new
12
+ end
13
+
14
+ # Declares a block that will be executed when a Sidekiq component is fully
15
+ # loaded. If the component has already loaded, the block is executed
16
+ # immediately.
17
+ #
18
+ # Sidekiq.loader.on_load(:api) do
19
+ # # extend the sidekiq API
20
+ # end
21
+ #
22
+ def on_load(name, &block)
23
+ # we don't want to hold the lock while calling the block
24
+ to_run = nil
25
+
26
+ @lock.synchronize do
27
+ if @loaded.include?(name)
28
+ to_run = block
29
+ else
30
+ @load_hooks[name] << block
31
+ end
32
+ end
33
+
34
+ to_run&.call
35
+ nil
36
+ end
37
+
38
+ # Executes all blocks registered to +name+ via on_load.
39
+ #
40
+ # Sidekiq.loader.run_load_hooks(:api)
41
+ #
42
+ # In the case of the above example, it will execute all hooks registered for +:api+.
43
+ #
44
+ def run_load_hooks(name)
45
+ hks = @lock.synchronize do
46
+ @loaded << name
47
+ @load_hooks.delete(name)
48
+ end
49
+
50
+ hks&.each do |blk|
51
+ blk.call
52
+ rescue => ex
53
+ handle_exception(ex, hook: name)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "time"
5
+
6
+ module Sidekiq
7
+ module Context
8
+ def self.with(hash)
9
+ orig_context = current.dup
10
+ current.merge!(hash)
11
+ yield
12
+ ensure
13
+ Thread.current[:sidekiq_context] = orig_context
14
+ end
15
+
16
+ def self.current
17
+ Thread.current[:sidekiq_context] ||= {}
18
+ end
19
+
20
+ def self.add(k, v)
21
+ current[k] = v
22
+ end
23
+ end
24
+
25
+ class Logger < ::Logger
26
+ module Formatters
27
+ class Base < ::Logger::Formatter
28
+ COLORS = {
29
+ "DEBUG" => "\e[1;32mDEBUG\e[0m", # green
30
+ "INFO" => "\e[1;34mINFO \e[0m", # blue
31
+ "WARN" => "\e[1;33mWARN \e[0m", # yellow
32
+ "ERROR" => "\e[1;31mERROR\e[0m", # red
33
+ "FATAL" => "\e[1;35mFATAL\e[0m" # pink
34
+ }
35
+
36
+ def tid
37
+ Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
38
+ end
39
+
40
+ def format_context(ctxt = Sidekiq::Context.current)
41
+ (ctxt.size == 0) ? "" : " #{ctxt.map { |k, v|
42
+ case v
43
+ when Array
44
+ "#{k}=#{v.join(",")}"
45
+ else
46
+ "#{k}=#{v}"
47
+ end
48
+ }.join(" ")}"
49
+ end
50
+ end
51
+
52
+ class Pretty < Base
53
+ def call(severity, time, program_name, message)
54
+ "#{COLORS[severity]} #{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context}: #{message}\n"
55
+ end
56
+ end
57
+
58
+ class Plain < Base
59
+ def call(severity, time, program_name, message)
60
+ "#{severity} #{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context}: #{message}\n"
61
+ end
62
+ end
63
+
64
+ class WithoutTimestamp < Pretty
65
+ def call(severity, time, program_name, message)
66
+ "#{COLORS[severity]} pid=#{::Process.pid} tid=#{tid}#{format_context}: #{message}\n"
67
+ end
68
+ end
69
+
70
+ class JSON < Base
71
+ def call(severity, time, program_name, message)
72
+ hash = {
73
+ ts: time.utc.iso8601(3),
74
+ pid: ::Process.pid,
75
+ tid: tid,
76
+ lvl: severity,
77
+ msg: message
78
+ }
79
+ c = Sidekiq::Context.current
80
+ hash["ctx"] = c unless c.empty?
81
+
82
+ Sidekiq.dump_json(hash) << "\n"
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/processor"
4
+
5
+ module Sidekiq
6
+ ##
7
+ # The Manager is the central coordination point in Sidekiq, controlling
8
+ # the lifecycle of the Processors.
9
+ #
10
+ # Tasks:
11
+ #
12
+ # 1. start: Spin up Processors.
13
+ # 3. processor_died: Handle job failure, throw away Processor, create new one.
14
+ # 4. quiet: shutdown idle Processors.
15
+ # 5. stop: hard stop the Processors by deadline.
16
+ #
17
+ # Note that only the last task requires its own Thread since it has to monitor
18
+ # the shutdown process. The other tasks are performed by other threads.
19
+ #
20
+ class Manager
21
+ include Sidekiq::Component
22
+
23
+ attr_reader :workers
24
+ attr_reader :capsule
25
+
26
+ def initialize(capsule)
27
+ @config = @capsule = capsule
28
+ @count = capsule.concurrency
29
+ raise ArgumentError, "Concurrency of #{@count} is not supported" if @count < 1
30
+
31
+ @done = false
32
+ @workers = Set.new
33
+ @plock = Mutex.new
34
+ @count.times do
35
+ @workers << Processor.new(@config, &method(:processor_result))
36
+ end
37
+ end
38
+
39
+ def start
40
+ @workers.each(&:start)
41
+ end
42
+
43
+ def quiet
44
+ return if @done
45
+ @done = true
46
+
47
+ logger.info { "Terminating quiet threads for #{capsule.name} capsule" }
48
+ @workers.each(&:terminate)
49
+ end
50
+
51
+ def stop(deadline)
52
+ quiet
53
+
54
+ # some of the shutdown events can be async,
55
+ # we don't have any way to know when they're done but
56
+ # give them a little time to take effect
57
+ sleep PAUSE_TIME
58
+ return if @workers.empty?
59
+
60
+ logger.info { "Pausing to allow jobs to finish..." }
61
+ wait_for(deadline) { @workers.empty? }
62
+ return if @workers.empty?
63
+
64
+ hard_shutdown
65
+ ensure
66
+ capsule.stop
67
+ end
68
+
69
+ def processor_result(processor, reason = nil)
70
+ @plock.synchronize do
71
+ @workers.delete(processor)
72
+ if !@done && @count > @workers.size
73
+ p = Processor.new(@config, &method(:processor_result))
74
+ @workers << p
75
+ p.start
76
+ end
77
+ end
78
+ nil
79
+ end
80
+
81
+ def stopped?
82
+ @done
83
+ end
84
+
85
+ private
86
+
87
+ def hard_shutdown
88
+ # We've reached the timeout and we still have busy threads.
89
+ # They must die but their jobs shall live on.
90
+ cleanup = nil
91
+ @plock.synchronize do
92
+ cleanup = @workers.dup
93
+ end
94
+
95
+ if cleanup.size > 0
96
+ jobs = cleanup.map { |p| p.job }.compact
97
+
98
+ logger.warn { "Terminating #{cleanup.size} busy threads" }
99
+ logger.debug { "Jobs still in progress #{jobs.inspect}" }
100
+
101
+ # Re-enqueue unfinished jobs
102
+ # NOTE: You may notice that we may push a job back to redis before
103
+ # the thread is terminated. This is ok because Sidekiq's
104
+ # contract says that jobs are run AT LEAST once. Process termination
105
+ # is delayed until we're certain the jobs are back in Redis because
106
+ # it is worse to lose a job than to run it twice.
107
+ capsule.fetcher.bulk_requeue(jobs)
108
+ end
109
+
110
+ cleanup.each do |processor|
111
+ processor.kill
112
+ end
113
+
114
+ # when this method returns, we immediately call `exit` which may not give
115
+ # the remaining threads time to run `ensure` blocks, etc. We pause here up
116
+ # to 3 seconds to give threads a minimal amount of time to run `ensure` blocks.
117
+ deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + 3
118
+ wait_for(deadline) { @workers.empty? }
119
+ end
120
+
121
+ # hack for quicker development / testing environment #2774
122
+ PAUSE_TIME = $stdout.tty? ? 0.1 : 0.5
123
+
124
+ # Wait for the orblock to be true or the deadline passed.
125
+ def wait_for(deadline, &condblock)
126
+ remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
127
+ while remaining > PAUSE_TIME
128
+ return if condblock.call
129
+ sleep PAUSE_TIME
130
+ remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
131
+ end
132
+ end
133
+ end
134
+ end