inst-jobs 0.12.3 → 0.13.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 +4 -4
- data/lib/delayed/backend/active_record.rb +40 -3
- data/lib/delayed/backend/base.rb +7 -0
- data/lib/delayed/backend/redis/job.rb +13 -3
- data/lib/delayed/logging.rb +32 -0
- data/lib/delayed/settings.rb +28 -2
- data/lib/delayed/version.rb +1 -1
- data/lib/delayed/work_queue/parent_process.rb +24 -180
- data/lib/delayed/work_queue/parent_process/client.rb +54 -0
- data/lib/delayed/work_queue/parent_process/server.rb +200 -0
- data/lib/delayed_job.rb +1 -0
- data/spec/active_record_job_spec.rb +28 -0
- data/spec/delayed/settings_spec.rb +7 -0
- data/spec/delayed/work_queue/parent_process/client_spec.rb +102 -0
- data/spec/delayed/work_queue/parent_process/server_spec.rb +162 -0
- data/spec/delayed/work_queue/parent_process_spec.rb +29 -164
- data/spec/gemfiles/42.gemfile.lock +44 -46
- data/spec/gemfiles/50.gemfile.lock +48 -48
- data/spec/shared/shared_backend.rb +17 -0
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 59c2643528ac5d09160a35e1d4fd338d058b593d
|
|
4
|
+
data.tar.gz: 69e5d68901df56c91fc10df1eaafb973877ef9fa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e9b5d4be7a15c770a825beffc9d175a0f6b6318e6df899af14995f90f52dc24f23adc7b4f90321e8cb9eba2818ed0ac7889394d382ce86b18fe58d61aef91f60
|
|
7
|
+
data.tar.gz: 31d950b9b5bc5d843f9ed04f316f0ce076b3ba4e32f5284c499a1ed1cd4d4ab36d674246fdb31fdefde8d25b02a42e84e3568f5af567fb35637250a91e37f1c3
|
|
@@ -16,6 +16,13 @@ module Delayed
|
|
|
16
16
|
class Job < ::ActiveRecord::Base
|
|
17
17
|
include Delayed::Backend::Base
|
|
18
18
|
self.table_name = :delayed_jobs
|
|
19
|
+
# Rails hasn't completely loaded yet, and setting the table name will cache some stuff
|
|
20
|
+
# so reset that cache so that it will load correctly after Rails is all loaded
|
|
21
|
+
# It's fixed in Rails 5 to not cache anything when you set the table_name
|
|
22
|
+
if Rails.version < '5' && Rails.version >= '4.2'
|
|
23
|
+
@arel_engine = nil
|
|
24
|
+
@arel_table = nil
|
|
25
|
+
end
|
|
19
26
|
|
|
20
27
|
def self.reconnect!
|
|
21
28
|
clear_all_connections!
|
|
@@ -201,7 +208,9 @@ module Delayed
|
|
|
201
208
|
def self.get_and_lock_next_available(worker_names,
|
|
202
209
|
queue = Delayed::Settings.queue,
|
|
203
210
|
min_priority = nil,
|
|
204
|
-
max_priority = nil
|
|
211
|
+
max_priority = nil,
|
|
212
|
+
extra_jobs: 0,
|
|
213
|
+
extra_jobs_owner: nil)
|
|
205
214
|
|
|
206
215
|
check_queue(queue)
|
|
207
216
|
check_priorities(min_priority, max_priority)
|
|
@@ -216,7 +225,7 @@ module Delayed
|
|
|
216
225
|
effective_worker_names = Array(worker_names)
|
|
217
226
|
|
|
218
227
|
target_jobs = all_available(queue, min_priority, max_priority).
|
|
219
|
-
limit(effective_worker_names.length).
|
|
228
|
+
limit(effective_worker_names.length + extra_jobs).
|
|
220
229
|
lock
|
|
221
230
|
jobs_with_row_number = all.from(target_jobs).
|
|
222
231
|
select("id, ROW_NUMBER() OVER () AS row_number")
|
|
@@ -224,6 +233,9 @@ module Delayed
|
|
|
224
233
|
effective_worker_names.each_with_index do |worker, i|
|
|
225
234
|
updates << "WHEN #{i + 1} THEN #{connection.quote(worker)} "
|
|
226
235
|
end
|
|
236
|
+
if extra_jobs_owner
|
|
237
|
+
updates << "ELSE #{connection.quote(extra_jobs_owner)} "
|
|
238
|
+
end
|
|
227
239
|
updates << "END, locked_at = #{connection.quote(db_time_now)}"
|
|
228
240
|
# joins and returning in an update! just bypass AR
|
|
229
241
|
query = "UPDATE #{quoted_table_name} SET #{updates} FROM (#{jobs_with_row_number.to_sql}) j2 WHERE j2.id=delayed_jobs.id RETURNING delayed_jobs.*"
|
|
@@ -231,7 +243,14 @@ module Delayed
|
|
|
231
243
|
# because this is an atomic query, we don't have to return more jobs than we needed
|
|
232
244
|
# to try and lock them, nor is there a possibility we need to try again because
|
|
233
245
|
# all of the jobs we tried to lock had already been locked by someone else
|
|
234
|
-
|
|
246
|
+
if worker_names.is_a?(Array)
|
|
247
|
+
result = jobs.index_by(&:locked_by)
|
|
248
|
+
# all of the extras can come back as an array
|
|
249
|
+
result[extra_jobs_owner] = jobs.select { |j| j.locked_by == extra_jobs_owner } if extra_jobs_owner
|
|
250
|
+
return result
|
|
251
|
+
else
|
|
252
|
+
return jobs.first
|
|
253
|
+
end
|
|
235
254
|
else
|
|
236
255
|
batch_size = Settings.fetch_batch_size
|
|
237
256
|
batch_size *= worker_names.length if worker_names.is_a?(Array)
|
|
@@ -314,6 +333,12 @@ module Delayed
|
|
|
314
333
|
end
|
|
315
334
|
end
|
|
316
335
|
|
|
336
|
+
def self.unlock(jobs)
|
|
337
|
+
unlocked = where(id: jobs).update_all(locked_at: nil, locked_by: nil)
|
|
338
|
+
jobs.each(&:unlock)
|
|
339
|
+
unlocked
|
|
340
|
+
end
|
|
341
|
+
|
|
317
342
|
# Lock this job for this worker.
|
|
318
343
|
# Returns true if we have the lock, false otherwise.
|
|
319
344
|
#
|
|
@@ -332,6 +357,18 @@ module Delayed
|
|
|
332
357
|
end
|
|
333
358
|
end
|
|
334
359
|
|
|
360
|
+
def transfer_lock!(from:, to:)
|
|
361
|
+
now = self.class.db_time_now
|
|
362
|
+
# We don't own this job so we will update the locked_by name and the locked_at
|
|
363
|
+
affected_rows = self.class.where(id: self, locked_by: from).update_all(locked_at: now, locked_by: to)
|
|
364
|
+
if affected_rows == 1
|
|
365
|
+
mark_as_locked!(now, to)
|
|
366
|
+
return true
|
|
367
|
+
else
|
|
368
|
+
return false
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
335
372
|
def mark_as_locked!(time, worker)
|
|
336
373
|
self.locked_at = time
|
|
337
374
|
self.locked_by = worker
|
data/lib/delayed/backend/base.rb
CHANGED
|
@@ -118,6 +118,13 @@ module Delayed
|
|
|
118
118
|
Time.now.utc
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
+
def unlock_orphaned_pending_jobs
|
|
122
|
+
horizon = db_time_now - Settings.parent_process[:pending_jobs_idle_timeout] * 4
|
|
123
|
+
orphaned_jobs = running_jobs.select { |job| job.locked_by.start_with?('work_queue:') && job.locked_at < horizon }
|
|
124
|
+
return 0 if orphaned_jobs.empty?
|
|
125
|
+
unlock(orphaned_jobs)
|
|
126
|
+
end
|
|
127
|
+
|
|
121
128
|
def unlock_orphaned_jobs(pid = nil, name = nil)
|
|
122
129
|
begin
|
|
123
130
|
name ||= Socket.gethostname
|
|
@@ -221,7 +221,9 @@ class Job
|
|
|
221
221
|
def self.get_and_lock_next_available(worker_name,
|
|
222
222
|
queue = Delayed::Settings.queue,
|
|
223
223
|
min_priority = Delayed::MIN_PRIORITY,
|
|
224
|
-
max_priority = Delayed::MAX_PRIORITY
|
|
224
|
+
max_priority = Delayed::MAX_PRIORITY,
|
|
225
|
+
extra_jobs: nil,
|
|
226
|
+
extra_jobs_owner: nil)
|
|
225
227
|
|
|
226
228
|
check_queue(queue)
|
|
227
229
|
check_priorities(min_priority, max_priority)
|
|
@@ -352,9 +354,18 @@ class Job
|
|
|
352
354
|
self.create!(options.merge(:singleton => true))
|
|
353
355
|
end
|
|
354
356
|
|
|
357
|
+
def self.unlock(jobs)
|
|
358
|
+
jobs.each(&:unlock!)
|
|
359
|
+
jobs.length
|
|
360
|
+
end
|
|
361
|
+
|
|
355
362
|
# not saved, just used as a marker when creating
|
|
356
363
|
attr_accessor :singleton
|
|
357
364
|
|
|
365
|
+
def transfer_lock!(from:, to:)
|
|
366
|
+
lock_in_redis!(to)
|
|
367
|
+
end
|
|
368
|
+
|
|
358
369
|
def lock_in_redis!(worker_name)
|
|
359
370
|
self.locked_at = self.class.db_time_now
|
|
360
371
|
self.locked_by = worker_name
|
|
@@ -362,8 +373,7 @@ class Job
|
|
|
362
373
|
end
|
|
363
374
|
|
|
364
375
|
def unlock!
|
|
365
|
-
|
|
366
|
-
self.locked_by = nil
|
|
376
|
+
unlock
|
|
367
377
|
save!
|
|
368
378
|
end
|
|
369
379
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'date'
|
|
2
|
+
|
|
3
|
+
module Delayed
|
|
4
|
+
module Logging
|
|
5
|
+
TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%S.%6N'.freeze
|
|
6
|
+
private_constant :TIMESTAMP_FORMAT
|
|
7
|
+
|
|
8
|
+
FORMAT = '%s - %s'
|
|
9
|
+
private_constant :FORMAT
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def self.logger
|
|
13
|
+
return @logger if @logger
|
|
14
|
+
if defined?(Rails.logger) && Rails.logger
|
|
15
|
+
@logger = Rails.logger
|
|
16
|
+
else
|
|
17
|
+
@logger = ::Logger.new(STDOUT).tap do |logger|
|
|
18
|
+
logger.formatter = ->(_, time, _, msg) {
|
|
19
|
+
FORMAT % [
|
|
20
|
+
time.strftime(TIMESTAMP_FORMAT),
|
|
21
|
+
msg
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def logger
|
|
29
|
+
Delayed::Logging.logger
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/delayed/settings.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'yaml'
|
|
2
2
|
require 'erb'
|
|
3
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
|
3
4
|
|
|
4
5
|
module Delayed
|
|
5
6
|
module Settings
|
|
@@ -17,7 +18,6 @@ module Delayed
|
|
|
17
18
|
:disable_periodic_jobs,
|
|
18
19
|
:disable_automatic_orphan_unlocking,
|
|
19
20
|
:last_ditch_logfile,
|
|
20
|
-
:parent_process_client_timeout,
|
|
21
21
|
]
|
|
22
22
|
SETTINGS_WITH_ARGS = [ :num_strands ]
|
|
23
23
|
|
|
@@ -32,6 +32,22 @@ module Delayed
|
|
|
32
32
|
|
|
33
33
|
mattr_accessor(*SETTINGS_WITH_ARGS)
|
|
34
34
|
|
|
35
|
+
PARENT_PROCESS_DEFAULTS = {
|
|
36
|
+
server_receive_timeout: 10.0,
|
|
37
|
+
server_socket_timeout: 10.0,
|
|
38
|
+
pending_jobs_idle_timeout: 30.0,
|
|
39
|
+
|
|
40
|
+
client_connect_timeout: 2.0,
|
|
41
|
+
client_receive_timeout: 10.0,
|
|
42
|
+
|
|
43
|
+
# We'll accept a partial, relative path and assume we want it inside
|
|
44
|
+
# Rails.root with inst-jobs.sock appended if provided a directory.
|
|
45
|
+
server_address: 'tmp',
|
|
46
|
+
}.with_indifferent_access.freeze
|
|
47
|
+
|
|
48
|
+
mattr_reader(:parent_process)
|
|
49
|
+
@@parent_process = PARENT_PROCESS_DEFAULTS.dup
|
|
50
|
+
|
|
35
51
|
def self.queue=(queue_name)
|
|
36
52
|
raise(ArgumentError, "queue_name must not be blank") if queue_name.blank?
|
|
37
53
|
@@queue = queue_name
|
|
@@ -44,7 +60,6 @@ module Delayed
|
|
|
44
60
|
self.fetch_batch_size = 5
|
|
45
61
|
self.select_random_from_batch = false
|
|
46
62
|
self.silence_periodic_log = false
|
|
47
|
-
self.parent_process_client_timeout = 10.0
|
|
48
63
|
|
|
49
64
|
self.num_strands = ->(strand_name){ nil }
|
|
50
65
|
self.default_job_options = ->{ Hash.new }
|
|
@@ -71,6 +86,8 @@ module Delayed
|
|
|
71
86
|
SETTINGS.each do |setting|
|
|
72
87
|
self.send("#{setting}=", config[setting.to_s]) if config.key?(setting.to_s)
|
|
73
88
|
end
|
|
89
|
+
parent_process.client_timeout = config['parent_process_client_timeout'] if config.key?('parent_process_client_timeout')
|
|
90
|
+
parent_process = config['parent_process'] if config.key?('parent_process')
|
|
74
91
|
end
|
|
75
92
|
|
|
76
93
|
def self.default_worker_config_name
|
|
@@ -86,5 +103,14 @@ module Delayed
|
|
|
86
103
|
end
|
|
87
104
|
File.expand_path("../#{path}", root)
|
|
88
105
|
end
|
|
106
|
+
|
|
107
|
+
def self.parent_process_client_timeout=(val)
|
|
108
|
+
parent_process['server_socket_timeout'] = Integer(val)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def self.parent_process=(new_config)
|
|
112
|
+
raise 'Parent process configurations must be a hash!' unless Hash === new_config
|
|
113
|
+
@@parent_process = PARENT_PROCESS_DEFAULTS.merge(new_config)
|
|
114
|
+
end
|
|
89
115
|
end
|
|
90
116
|
end
|
data/lib/delayed/version.rb
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
require 'pathname'
|
|
1
2
|
require 'socket'
|
|
2
|
-
require 'tempfile'
|
|
3
3
|
require 'timeout'
|
|
4
4
|
|
|
5
|
+
require_relative 'parent_process/client'
|
|
6
|
+
require_relative 'parent_process/server'
|
|
7
|
+
|
|
5
8
|
module Delayed
|
|
6
9
|
module WorkQueue
|
|
7
10
|
# ParentProcess is a WorkQueue implementation that spawns a separate worker
|
|
@@ -24,200 +27,41 @@ class ParentProcess
|
|
|
24
27
|
class ProtocolError < RuntimeError
|
|
25
28
|
end
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
attr_reader :server_address
|
|
31
|
+
|
|
32
|
+
DEFAULT_SOCKET_NAME = 'inst-jobs.sock'.freeze
|
|
33
|
+
private_constant :DEFAULT_SOCKET_NAME
|
|
30
34
|
|
|
31
|
-
def
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# write a unix socket file to the same location, we lose the hard uniqueness
|
|
35
|
-
# guarantees of Tempfile. This is OK for this use case, we only generate one
|
|
36
|
-
# Tempfile with this prefix.
|
|
37
|
-
tmp = Tempfile.new("inst-jobs-#{Process.pid}-")
|
|
38
|
-
path = tmp.path
|
|
39
|
-
tmp.close!
|
|
40
|
-
path
|
|
35
|
+
def initialize(config = Settings.parent_process)
|
|
36
|
+
@config = config
|
|
37
|
+
@server_address = generate_socket_path(config['server_address'])
|
|
41
38
|
end
|
|
42
39
|
|
|
43
40
|
def server(parent_pid: nil)
|
|
44
41
|
# The unix_server_socket method takes care of cleaning up any existing
|
|
45
42
|
# socket for us if the work queue process dies and is restarted.
|
|
46
|
-
listen_socket = Socket.unix_server_socket(@
|
|
47
|
-
Server.new(listen_socket, parent_pid: parent_pid)
|
|
43
|
+
listen_socket = Socket.unix_server_socket(@server_address)
|
|
44
|
+
Server.new(listen_socket, parent_pid: parent_pid, config: @config)
|
|
48
45
|
end
|
|
49
46
|
|
|
50
47
|
def client
|
|
51
|
-
Client.new(Addrinfo.unix(@
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
module SayUtil
|
|
55
|
-
def say(msg, level = :debug)
|
|
56
|
-
if defined?(Rails.logger) && Rails.logger
|
|
57
|
-
message = -> { "[#{Process.pid}]Q #{msg}" }
|
|
58
|
-
Rails.logger.send(level, self.class.name, &message)
|
|
59
|
-
else
|
|
60
|
-
puts(msg)
|
|
61
|
-
end
|
|
62
|
-
end
|
|
48
|
+
Client.new(Addrinfo.unix(@server_address), config: @config)
|
|
63
49
|
end
|
|
64
50
|
|
|
65
|
-
|
|
66
|
-
attr_reader :addrinfo
|
|
51
|
+
private
|
|
67
52
|
|
|
68
|
-
|
|
53
|
+
def generate_socket_path(supplied_path)
|
|
54
|
+
pathname = Pathname.new(supplied_path)
|
|
69
55
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
say("Requesting work using #{@socket.inspect}")
|
|
77
|
-
Marshal.dump([worker_name, worker_config], @socket)
|
|
78
|
-
response = Marshal.load(@socket)
|
|
79
|
-
unless response.nil? || (response.is_a?(Delayed::Job) && response.locked_by == worker_name)
|
|
80
|
-
say("Received invalid response from server: #{response.inspect}")
|
|
81
|
-
raise(ProtocolError, "response is not a locked job: #{response.inspect}")
|
|
82
|
-
end
|
|
83
|
-
say("Received work from server: #{response.inspect}")
|
|
84
|
-
response
|
|
85
|
-
rescue SystemCallError, IOError => ex
|
|
86
|
-
say("Work queue connection lost, reestablishing on next poll. (#{ex})", :error)
|
|
87
|
-
# The work queue process died. Return nil to signal the worker
|
|
88
|
-
# process should sleep as if no job was found, and then retry.
|
|
89
|
-
@socket = nil
|
|
90
|
-
nil
|
|
56
|
+
if pathname.absolute? && pathname.directory?
|
|
57
|
+
pathname.join(DEFAULT_SOCKET_NAME).to_s
|
|
58
|
+
elsif pathname.absolute?
|
|
59
|
+
supplied_path
|
|
60
|
+
else
|
|
61
|
+
generate_socket_path(Settings.expand_rails_path(supplied_path))
|
|
91
62
|
end
|
|
92
63
|
end
|
|
93
|
-
|
|
94
|
-
class Server
|
|
95
|
-
attr_reader :listen_socket
|
|
96
|
-
|
|
97
|
-
include SayUtil
|
|
98
|
-
|
|
99
|
-
def initialize(listen_socket, parent_pid: nil)
|
|
100
|
-
@listen_socket = listen_socket
|
|
101
|
-
@parent_pid = parent_pid
|
|
102
|
-
@clients = {}
|
|
103
|
-
@waiting_clients = {}
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def connected_clients
|
|
107
|
-
@clients.size
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def all_workers_idle?
|
|
111
|
-
!@clients.any? { |_, c| c.working }
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# run the server queue worker
|
|
115
|
-
# this method does not return, only exits or raises an exception
|
|
116
|
-
def run
|
|
117
|
-
say "Starting work queue process"
|
|
118
|
-
|
|
119
|
-
while !exit?
|
|
120
|
-
run_once
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
rescue => e
|
|
124
|
-
say "WorkQueue Server died: #{e.inspect}", :error
|
|
125
|
-
raise
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def run_once
|
|
129
|
-
handles = @clients.keys + [@listen_socket]
|
|
130
|
-
timeout = Settings.sleep_delay + (rand * Settings.sleep_delay_stagger)
|
|
131
|
-
readable, _, _ = IO.select(handles, nil, nil, timeout)
|
|
132
|
-
if readable
|
|
133
|
-
readable.each { |s| handle_read(s) }
|
|
134
|
-
end
|
|
135
|
-
check_for_work
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def handle_read(socket)
|
|
139
|
-
if socket == @listen_socket
|
|
140
|
-
handle_accept
|
|
141
|
-
else
|
|
142
|
-
handle_request(socket)
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# Any error on the listen socket other than WaitReadable will bubble up
|
|
147
|
-
# and terminate the work queue process, to be restarted by the parent daemon.
|
|
148
|
-
def handle_accept
|
|
149
|
-
socket, _addr = @listen_socket.accept_nonblock
|
|
150
|
-
if socket
|
|
151
|
-
@clients[socket] = ClientState.new(false, socket)
|
|
152
|
-
end
|
|
153
|
-
rescue IO::WaitReadable
|
|
154
|
-
say("Server attempted to read listen_socket but failed with IO::WaitReadable", :error)
|
|
155
|
-
# ignore and just try accepting again next time through the loop
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def handle_request(socket)
|
|
159
|
-
# There is an assumption here that the client will never send a partial
|
|
160
|
-
# request and then leave the socket open. Doing so would leave us hanging
|
|
161
|
-
# here forever. This is only a reasonable assumption because we control
|
|
162
|
-
# the client.
|
|
163
|
-
worker_name, worker_config = client_timeout { Marshal.load(socket) }
|
|
164
|
-
client = @clients[socket]
|
|
165
|
-
client.name = worker_name
|
|
166
|
-
client.working = false
|
|
167
|
-
(@waiting_clients[worker_config] ||= []) << client
|
|
168
|
-
|
|
169
|
-
rescue SystemCallError, IOError, Timeout::Error => ex
|
|
170
|
-
say("Receiving message from client (#{socket}) failed: #{ex.inspect}", :error)
|
|
171
|
-
drop_socket(socket)
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
def check_for_work
|
|
175
|
-
@waiting_clients.each do |(worker_config, workers)|
|
|
176
|
-
next if workers.empty?
|
|
177
|
-
|
|
178
|
-
Delayed::Worker.lifecycle.run_callbacks(:work_queue_pop, self, worker_config) do
|
|
179
|
-
response = Delayed::Job.get_and_lock_next_available(
|
|
180
|
-
workers.map(&:name),
|
|
181
|
-
worker_config[:queue],
|
|
182
|
-
worker_config[:min_priority],
|
|
183
|
-
worker_config[:max_priority])
|
|
184
|
-
response.each do |(worker_name, job)|
|
|
185
|
-
client = workers.find { |worker| worker.name == worker_name }
|
|
186
|
-
client.working = true
|
|
187
|
-
@waiting_clients[worker_config].delete(client)
|
|
188
|
-
begin
|
|
189
|
-
client_timeout { Marshal.dump(job, client.socket) }
|
|
190
|
-
rescue SystemCallError, IOError, Timeout::Error
|
|
191
|
-
drop_socket(client.socket)
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def drop_socket(socket)
|
|
199
|
-
# this socket went away
|
|
200
|
-
begin
|
|
201
|
-
socket.close
|
|
202
|
-
rescue IOError
|
|
203
|
-
end
|
|
204
|
-
@clients.delete(socket)
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
def exit?
|
|
208
|
-
parent_exited?
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
def parent_exited?
|
|
212
|
-
@parent_pid && @parent_pid != Process.ppid
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
def client_timeout
|
|
216
|
-
Timeout.timeout(Settings.parent_process_client_timeout) { yield }
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
ClientState = Struct.new(:working, :socket, :name)
|
|
220
|
-
end
|
|
221
64
|
end
|
|
222
65
|
end
|
|
223
66
|
end
|
|
67
|
+
|