solid-jobs 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +27 -0
- data/LICENSE.txt +22 -0
- data/README.md +159 -0
- data/docs/reliability.md +114 -0
- data/exe/solid-jobs +8 -0
- data/lib/active_job/queue_adapters/solid_jobs_adapter.rb +42 -0
- data/lib/solid_jobs/active_job.rb +16 -0
- data/lib/solid_jobs/api.rb +457 -0
- data/lib/solid_jobs/cli.rb +135 -0
- data/lib/solid_jobs/client.rb +202 -0
- data/lib/solid_jobs/config.rb +171 -0
- data/lib/solid_jobs/errors.rb +9 -0
- data/lib/solid_jobs/fetch.rb +310 -0
- data/lib/solid_jobs/heartbeat.rb +60 -0
- data/lib/solid_jobs/integrity_check.rb +222 -0
- data/lib/solid_jobs/job.rb +156 -0
- data/lib/solid_jobs/middleware/chain.rb +104 -0
- data/lib/solid_jobs/processor.rb +152 -0
- data/lib/solid_jobs/ractor_support.rb +11 -0
- data/lib/solid_jobs/rails.rb +16 -0
- data/lib/solid_jobs/recovery.rb +65 -0
- data/lib/solid_jobs/retry_service.rb +100 -0
- data/lib/solid_jobs/scheduler.rb +64 -0
- data/lib/solid_jobs/server.rb +221 -0
- data/lib/solid_jobs/startup_barrier.rb +126 -0
- data/lib/solid_jobs/testing.rb +69 -0
- data/lib/solid_jobs/utilities.rb +52 -0
- data/lib/solid_jobs/version.rb +6 -0
- data/lib/solid_jobs/worker.rb +35 -0
- data/lib/solid_jobs.rb +92 -0
- metadata +189 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module SolidJobs
|
|
6
|
+
class Stats
|
|
7
|
+
attr_reader :config
|
|
8
|
+
|
|
9
|
+
def initialize(config: SolidJobs.config)
|
|
10
|
+
@config = config
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def processed
|
|
14
|
+
integer("stat:processed")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def failed
|
|
18
|
+
integer("stat:failed")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def enqueued
|
|
22
|
+
queues.sum(&:size)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def scheduled_size
|
|
26
|
+
integer_command("ZCARD", "schedule")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def retry_size
|
|
30
|
+
integer_command("ZCARD", "retry")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def dead_size
|
|
34
|
+
integer_command("ZCARD", "dead")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def processes_size
|
|
38
|
+
integer_command("SCARD", "processes")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def workers_size
|
|
42
|
+
ProcessSet.new(config: config).sum(&:busy)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def reset
|
|
46
|
+
config.redis_pool.call("DEL", "stat:processed", "stat:failed")
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def queues
|
|
53
|
+
Queue.all(config: config)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def integer(key)
|
|
57
|
+
Integer(config.redis_pool.call("GET", key) || 0)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def integer_command(*command)
|
|
61
|
+
Integer(config.redis_pool.call(*command))
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class Queue
|
|
66
|
+
include Enumerable
|
|
67
|
+
|
|
68
|
+
attr_reader :name
|
|
69
|
+
|
|
70
|
+
def self.all(config: SolidJobs.config)
|
|
71
|
+
Array(config.redis_pool.call("SMEMBERS", "queues")).sort.map do |name|
|
|
72
|
+
new(name, config: config)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def initialize(name, config: SolidJobs.config)
|
|
77
|
+
@name = String(name)
|
|
78
|
+
@config = config
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def each
|
|
82
|
+
return enum_for(:each) unless block_given?
|
|
83
|
+
|
|
84
|
+
Array(@config.redis_pool.call("LRANGE", key, 0, -1)).reverse_each do |raw|
|
|
85
|
+
yield JobRecord.new(raw, config: @config, container: self)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def size
|
|
90
|
+
Integer(@config.redis_pool.call("LLEN", key))
|
|
91
|
+
end
|
|
92
|
+
alias_method :length, :size
|
|
93
|
+
|
|
94
|
+
def latency
|
|
95
|
+
raw = @config.redis_pool.call("LINDEX", key, -1)
|
|
96
|
+
return 0.0 unless raw
|
|
97
|
+
|
|
98
|
+
payload = JSON.parse(raw)
|
|
99
|
+
enqueued_at = payload["enqueued_at"] || payload["created_at"]
|
|
100
|
+
return 0.0 unless enqueued_at
|
|
101
|
+
|
|
102
|
+
[Time.now.to_f - Float(enqueued_at) / 1_000, 0.0].max
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def clear
|
|
106
|
+
@config.redis_pool.call("DEL", key)
|
|
107
|
+
self
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def delete
|
|
111
|
+
@config.redis_pool.pipelined do |pipeline|
|
|
112
|
+
pipeline.call("DEL", key)
|
|
113
|
+
pipeline.call("SREM", "queues", name)
|
|
114
|
+
end
|
|
115
|
+
self
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def paused?
|
|
119
|
+
Array(@config.redis_pool.call("SISMEMBER", "paused", name)).first.to_i == 1
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def pause
|
|
123
|
+
@config.redis_pool.call("SADD", "paused", name)
|
|
124
|
+
self
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def unpause
|
|
128
|
+
@config.redis_pool.call("SREM", "paused", name)
|
|
129
|
+
self
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
def key
|
|
135
|
+
"queue:#{name}"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
class JobRecord
|
|
140
|
+
attr_reader :item, :score
|
|
141
|
+
|
|
142
|
+
def initialize(raw, score: nil, config: SolidJobs.config, container: nil)
|
|
143
|
+
@raw = raw
|
|
144
|
+
@item = JSON.parse(raw)
|
|
145
|
+
@score = Float(score) if score
|
|
146
|
+
@config = config
|
|
147
|
+
@container = container
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def jid
|
|
151
|
+
item["jid"]
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def queue
|
|
155
|
+
item["queue"]
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def klass
|
|
159
|
+
item["class"]
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def args
|
|
163
|
+
item["args"]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def created_at
|
|
167
|
+
milliseconds_time(item["created_at"])
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def enqueued_at
|
|
171
|
+
milliseconds_time(item["enqueued_at"])
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def at
|
|
175
|
+
Time.at(score) if score
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def delete
|
|
179
|
+
case @container
|
|
180
|
+
when SortedSet
|
|
181
|
+
@config.redis_pool.call("ZREM", @container.name, @raw)
|
|
182
|
+
when Queue
|
|
183
|
+
@config.redis_pool.call("LREM", "queue:#{@container.name}", 1, @raw)
|
|
184
|
+
else
|
|
185
|
+
false
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def retry
|
|
190
|
+
delete
|
|
191
|
+
Client.new(config: @config).push(item)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
private
|
|
195
|
+
|
|
196
|
+
def milliseconds_time(value)
|
|
197
|
+
Time.at(Float(value) / 1_000) if value
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
class SortedSet
|
|
202
|
+
include Enumerable
|
|
203
|
+
|
|
204
|
+
attr_reader :name
|
|
205
|
+
|
|
206
|
+
def initialize(name, config: SolidJobs.config)
|
|
207
|
+
@name = name
|
|
208
|
+
@config = config
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def each
|
|
212
|
+
return enum_for(:each) unless block_given?
|
|
213
|
+
|
|
214
|
+
values = Array(@config.redis_pool.call("ZRANGE", name, 0, -1, "WITHSCORES"))
|
|
215
|
+
values.each_slice(2) do |raw, score|
|
|
216
|
+
yield JobRecord.new(raw, score: score, config: @config, container: self)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def size
|
|
221
|
+
Integer(@config.redis_pool.call("ZCARD", name))
|
|
222
|
+
end
|
|
223
|
+
alias_method :length, :size
|
|
224
|
+
|
|
225
|
+
def clear
|
|
226
|
+
@config.redis_pool.call("DEL", name)
|
|
227
|
+
self
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def fetch(*jids)
|
|
231
|
+
wanted = jids.flatten
|
|
232
|
+
select { |job| wanted.include?(job.jid) }
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
class ScheduledSet < SortedSet
|
|
237
|
+
def initialize(config: SolidJobs.config)
|
|
238
|
+
super("schedule", config: config)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
class RetrySet < SortedSet
|
|
243
|
+
def initialize(config: SolidJobs.config)
|
|
244
|
+
super("retry", config: config)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def retry_all
|
|
248
|
+
each.to_a.each(&:retry)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
class DeadSet < SortedSet
|
|
253
|
+
def initialize(config: SolidJobs.config)
|
|
254
|
+
super("dead", config: config)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def retry_all
|
|
258
|
+
each.to_a.each(&:retry)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
class Process
|
|
263
|
+
attr_reader :identity
|
|
264
|
+
|
|
265
|
+
def initialize(identity, config: SolidJobs.config)
|
|
266
|
+
@identity = identity
|
|
267
|
+
@config = config
|
|
268
|
+
fields = Array(@config.redis_pool.call("HGETALL", identity))
|
|
269
|
+
@attributes = fields.each_slice(2).to_h
|
|
270
|
+
@info = JSON.parse(@attributes.fetch("info", "{}"))
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def hostname
|
|
274
|
+
@info["hostname"]
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def pid
|
|
278
|
+
Integer(@info["pid"] || 0)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def queues
|
|
282
|
+
@info["queues"] || []
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def busy
|
|
286
|
+
Integer(@attributes["busy"] || 0)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def concurrency
|
|
290
|
+
Integer(@attributes["concurrency"] || 0)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def quiet?
|
|
294
|
+
@attributes["quiet"] == "true"
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def beat
|
|
298
|
+
Time.at(Float(@attributes["beat"])) if @attributes["beat"]
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def quiet!
|
|
302
|
+
signal("TSTP")
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def stop!
|
|
306
|
+
signal("TERM")
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def dump_threads
|
|
310
|
+
signal("TTIN")
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def workers
|
|
314
|
+
fields = Array(@config.redis_pool.call("HGETALL", "#{identity}:work"))
|
|
315
|
+
fields.each_slice(2).map do |processor_id, raw|
|
|
316
|
+
Work.new(identity, processor_id, raw)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
private
|
|
321
|
+
|
|
322
|
+
def signal(name)
|
|
323
|
+
@config.redis_pool.call("LPUSH", "#{identity}-signals", name)
|
|
324
|
+
true
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
class ProcessSet
|
|
329
|
+
include Enumerable
|
|
330
|
+
|
|
331
|
+
def initialize(config: SolidJobs.config)
|
|
332
|
+
@config = config
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def each
|
|
336
|
+
return enum_for(:each) unless block_given?
|
|
337
|
+
|
|
338
|
+
Array(@config.redis_pool.call("SMEMBERS", "processes")).each do |identity|
|
|
339
|
+
process = Process.new(identity, config: @config)
|
|
340
|
+
if process.beat
|
|
341
|
+
yield process
|
|
342
|
+
else
|
|
343
|
+
@config.redis_pool.call("SREM", "processes", identity)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def size
|
|
349
|
+
count
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
class Work
|
|
354
|
+
attr_reader :process_id, :processor_id
|
|
355
|
+
|
|
356
|
+
def initialize(process_id, processor_id, raw)
|
|
357
|
+
@process_id = process_id
|
|
358
|
+
@processor_id = processor_id
|
|
359
|
+
@attributes = JSON.parse(raw)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def queue
|
|
363
|
+
@attributes["queue"]
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def payload
|
|
367
|
+
@attributes["payload"]
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def run_at
|
|
371
|
+
Time.at(Float(@attributes["run_at"]))
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def reservation_id
|
|
375
|
+
@attributes["reservation_id"]
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def attempt
|
|
379
|
+
Integer(@attributes["attempt"] || 1)
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
class Reservation
|
|
384
|
+
attr_reader :attributes
|
|
385
|
+
|
|
386
|
+
def initialize(raw)
|
|
387
|
+
@attributes = JSON.parse(raw)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
%w[job_id reservation_id process_id worker_id queue].each do |name|
|
|
391
|
+
define_method(name) { attributes[name] }
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def attempt
|
|
395
|
+
Integer(attributes["attempt"])
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def reserved_at
|
|
399
|
+
Time.at(Float(attributes["reserved_at"]))
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
class ReservationSet
|
|
404
|
+
include Enumerable
|
|
405
|
+
|
|
406
|
+
def initialize(config: SolidJobs.config)
|
|
407
|
+
@config = config
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def each
|
|
411
|
+
return enum_for(:each) unless block_given?
|
|
412
|
+
|
|
413
|
+
keys.each do |key|
|
|
414
|
+
Array(@config.redis_pool.call("HVALS", key)).each do |raw|
|
|
415
|
+
yield Reservation.new(raw)
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def find_job(job_id)
|
|
421
|
+
find { |reservation| reservation.job_id == job_id }
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
private
|
|
425
|
+
|
|
426
|
+
def keys
|
|
427
|
+
cursor = "0"
|
|
428
|
+
found = []
|
|
429
|
+
loop do
|
|
430
|
+
cursor, batch = @config.redis_pool.call(
|
|
431
|
+
"SCAN", cursor, "MATCH", "*:reservations", "COUNT", 100,
|
|
432
|
+
)
|
|
433
|
+
found.concat(batch)
|
|
434
|
+
break if cursor == "0"
|
|
435
|
+
end
|
|
436
|
+
found
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
class Workers
|
|
441
|
+
include Enumerable
|
|
442
|
+
|
|
443
|
+
def initialize(config: SolidJobs.config)
|
|
444
|
+
@processes = ProcessSet.new(config: config)
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def each
|
|
448
|
+
return enum_for(:each) unless block_given?
|
|
449
|
+
|
|
450
|
+
@processes.each { |process| process.workers.each { |work| yield work } }
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
def size
|
|
454
|
+
count
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module SolidJobs
|
|
7
|
+
class CLI
|
|
8
|
+
SIGNALS = %w[INT TERM TSTP TTIN INFO].freeze
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@options = {queues: []}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run(arguments = ARGV)
|
|
15
|
+
parse(arguments)
|
|
16
|
+
load_configuration
|
|
17
|
+
load_application
|
|
18
|
+
apply_options
|
|
19
|
+
server = Server.new(config: SolidJobs.config).start
|
|
20
|
+
read_io, write_io = IO.pipe
|
|
21
|
+
handlers = install_signal_handlers(write_io)
|
|
22
|
+
wait_for_shutdown(server, read_io)
|
|
23
|
+
server.stop
|
|
24
|
+
0
|
|
25
|
+
ensure
|
|
26
|
+
handlers&.each { |signal, handler| Signal.trap(signal, handler) }
|
|
27
|
+
read_io&.close
|
|
28
|
+
write_io&.close
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def parse(arguments)
|
|
32
|
+
parser.parse!(arguments)
|
|
33
|
+
@options
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def parser
|
|
37
|
+
@parser ||= OptionParser.new do |options|
|
|
38
|
+
options.banner = "Usage: solid-jobs [options]"
|
|
39
|
+
options.on("-r", "--require PATH", "Require an application file") { |value| @options[:require] = value }
|
|
40
|
+
options.on("-C", "--config PATH", "Load YAML configuration") { |value| @options[:config] = value }
|
|
41
|
+
options.on("-c", "--concurrency N", Integer, "Processor Ractor count") { |value| @options[:concurrency] = value }
|
|
42
|
+
options.on("-q", "--queue QUEUE", "Queue name or name,weight") { |value| @options[:queues] << queue(value) }
|
|
43
|
+
options.on("-e", "--environment NAME", "Application environment") { |value| @options[:environment] = value }
|
|
44
|
+
options.on("-t", "--timeout SECONDS", Float, "Graceful shutdown timeout") { |value| @options[:timeout] = value }
|
|
45
|
+
options.on("-v", "--version", "Print version") do
|
|
46
|
+
puts "solid-jobs #{VERSION}"
|
|
47
|
+
exit
|
|
48
|
+
end
|
|
49
|
+
options.on("-h", "--help", "Print help") do
|
|
50
|
+
puts options
|
|
51
|
+
exit
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def load_configuration
|
|
59
|
+
return unless @options[:config]
|
|
60
|
+
|
|
61
|
+
values = YAML.safe_load_file(@options[:config], permitted_classes: [Symbol], aliases: true) || {}
|
|
62
|
+
values = values.fetch(@options[:environment], values) if @options[:environment]
|
|
63
|
+
@options[:concurrency] ||= values["concurrency"]
|
|
64
|
+
@options[:timeout] ||= values["timeout"]
|
|
65
|
+
@options[:queues] = Array(values["queues"]).map { |value| queue(value) } if @options[:queues].empty?
|
|
66
|
+
if (url = values.dig("redis", "url") || values["redis_url"])
|
|
67
|
+
SolidJobs.config.redis = SolidRedis::Config.new(url: url)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def load_application
|
|
72
|
+
return unless @options[:require]
|
|
73
|
+
|
|
74
|
+
require File.expand_path(@options[:require])
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def apply_options
|
|
78
|
+
ENV["RAILS_ENV"] = ENV["RACK_ENV"] = @options[:environment] if @options[:environment]
|
|
79
|
+
SolidJobs.config.concurrency = @options[:concurrency] if @options[:concurrency]
|
|
80
|
+
SolidJobs.config.queues = @options[:queues] unless @options[:queues].empty?
|
|
81
|
+
SolidJobs.config.shutdown_timeout = @options[:timeout] if @options[:timeout]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def install_signal_handlers(write_io)
|
|
85
|
+
SIGNALS.to_h do |signal|
|
|
86
|
+
previous = Signal.trap(signal) do
|
|
87
|
+
write_io.write_nonblock("#{signal}\n", exception: false)
|
|
88
|
+
end
|
|
89
|
+
[signal, previous]
|
|
90
|
+
rescue ArgumentError
|
|
91
|
+
[signal, "DEFAULT"]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def wait_for_shutdown(server, read_io)
|
|
96
|
+
loop do
|
|
97
|
+
if IO.select([read_io], nil, nil, 0.5)
|
|
98
|
+
signal = read_io.gets&.strip
|
|
99
|
+
return if handle_signal(server, signal)
|
|
100
|
+
end
|
|
101
|
+
remote = server.remote_signal
|
|
102
|
+
return if remote && handle_signal(server, remote)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def handle_signal(server, signal)
|
|
107
|
+
case signal
|
|
108
|
+
when "INT", "TERM"
|
|
109
|
+
true
|
|
110
|
+
when "TSTP"
|
|
111
|
+
server.quiet
|
|
112
|
+
false
|
|
113
|
+
when "TTIN", "INFO"
|
|
114
|
+
dump_threads
|
|
115
|
+
false
|
|
116
|
+
else
|
|
117
|
+
false
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def dump_threads
|
|
122
|
+
Thread.list.each do |thread|
|
|
123
|
+
SolidJobs.config.logger.info(
|
|
124
|
+
"Thread #{thread.object_id}: #{Array(thread.backtrace).join("\n")}",
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def queue(value)
|
|
130
|
+
name, weight = value.to_s.split(",", 2)
|
|
131
|
+
weight ? [name, Integer(weight)] : name
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|