chore-core 1.8.2 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +173 -150
- data/chore-core.gemspec +3 -3
- data/lib/chore.rb +31 -5
- data/lib/chore/cli.rb +22 -4
- data/lib/chore/configuration.rb +1 -1
- data/lib/chore/consumer.rb +54 -12
- data/lib/chore/fetcher.rb +12 -7
- data/lib/chore/hooks.rb +2 -1
- data/lib/chore/job.rb +19 -0
- data/lib/chore/manager.rb +18 -2
- data/lib/chore/publisher.rb +18 -2
- data/lib/chore/queues/filesystem/consumer.rb +126 -64
- data/lib/chore/queues/filesystem/filesystem_queue.rb +19 -0
- data/lib/chore/queues/filesystem/publisher.rb +13 -19
- data/lib/chore/queues/sqs.rb +22 -13
- data/lib/chore/queues/sqs/consumer.rb +64 -51
- data/lib/chore/queues/sqs/publisher.rb +26 -17
- data/lib/chore/strategies/consumer/batcher.rb +14 -15
- data/lib/chore/strategies/consumer/single_consumer_strategy.rb +5 -5
- data/lib/chore/strategies/consumer/threaded_consumer_strategy.rb +9 -7
- data/lib/chore/strategies/consumer/throttled_consumer_strategy.rb +120 -0
- data/lib/chore/strategies/worker/forked_worker_strategy.rb +5 -6
- data/lib/chore/strategies/worker/helpers/ipc.rb +87 -0
- data/lib/chore/strategies/worker/helpers/preforked_worker.rb +163 -0
- data/lib/chore/strategies/worker/helpers/work_distributor.rb +65 -0
- data/lib/chore/strategies/worker/helpers/worker_info.rb +13 -0
- data/lib/chore/strategies/worker/helpers/worker_killer.rb +40 -0
- data/lib/chore/strategies/worker/helpers/worker_manager.rb +183 -0
- data/lib/chore/strategies/worker/preforked_worker_strategy.rb +150 -0
- data/lib/chore/strategies/worker/single_worker_strategy.rb +35 -13
- data/lib/chore/unit_of_work.rb +10 -1
- data/lib/chore/util.rb +5 -1
- data/lib/chore/version.rb +3 -3
- data/lib/chore/worker.rb +32 -3
- data/spec/chore/cli_spec.rb +2 -2
- data/spec/chore/consumer_spec.rb +1 -5
- data/spec/chore/duplicate_detector_spec.rb +17 -5
- data/spec/chore/fetcher_spec.rb +0 -11
- data/spec/chore/manager_spec.rb +7 -0
- data/spec/chore/queues/filesystem/filesystem_consumer_spec.rb +74 -16
- data/spec/chore/queues/sqs/consumer_spec.rb +117 -78
- data/spec/chore/queues/sqs/publisher_spec.rb +49 -60
- data/spec/chore/queues/sqs_spec.rb +32 -41
- data/spec/chore/strategies/consumer/batcher_spec.rb +50 -0
- data/spec/chore/strategies/consumer/single_consumer_strategy_spec.rb +3 -3
- data/spec/chore/strategies/consumer/threaded_consumer_strategy_spec.rb +7 -6
- data/spec/chore/strategies/consumer/throttled_consumer_strategy_spec.rb +165 -0
- data/spec/chore/strategies/worker/forked_worker_strategy_spec.rb +17 -2
- data/spec/chore/strategies/worker/helpers/ipc_spec.rb +127 -0
- data/spec/chore/strategies/worker/helpers/preforked_worker_spec.rb +236 -0
- data/spec/chore/strategies/worker/helpers/work_distributor_spec.rb +131 -0
- data/spec/chore/strategies/worker/helpers/worker_info_spec.rb +14 -0
- data/spec/chore/strategies/worker/helpers/worker_killer_spec.rb +97 -0
- data/spec/chore/strategies/worker/helpers/worker_manager_spec.rb +304 -0
- data/spec/chore/strategies/worker/preforked_worker_strategy_spec.rb +183 -0
- data/spec/chore/strategies/worker/single_worker_strategy_spec.rb +25 -0
- data/spec/chore/worker_spec.rb +82 -14
- data/spec/spec_helper.rb +1 -1
- data/spec/support/queues/sqs/fake_objects.rb +18 -0
- metadata +39 -15
@@ -3,21 +3,26 @@ require 'chore/publisher'
|
|
3
3
|
module Chore
|
4
4
|
module Queues
|
5
5
|
module SQS
|
6
|
-
|
7
6
|
# SQS Publisher, for writing messages to SQS from Chore
|
8
7
|
class Publisher < Chore::Publisher
|
9
8
|
@@reset_next = true
|
10
9
|
|
10
|
+
# @param [Hash] opts Publisher options
|
11
11
|
def initialize(opts={})
|
12
12
|
super
|
13
13
|
@sqs_queues = {}
|
14
14
|
@sqs_queue_urls = {}
|
15
15
|
end
|
16
16
|
|
17
|
-
#
|
17
|
+
# Publishes a message to an SQS queue
|
18
|
+
#
|
19
|
+
# @param [String] queue_name Name of the SQS queue
|
20
|
+
# @param [Hash] job Job instance definition, will be encoded to JSON
|
21
|
+
#
|
22
|
+
# @return [struct Aws::SQS::Types::SendMessageResult]
|
18
23
|
def publish(queue_name,job)
|
19
|
-
queue =
|
20
|
-
queue.send_message(encode_job(job))
|
24
|
+
queue = queue(queue_name)
|
25
|
+
queue.send_message(message_body: encode_job(job))
|
21
26
|
end
|
22
27
|
|
23
28
|
# Sets a flag that instructs the publisher to reset the connection the next time it's used
|
@@ -25,29 +30,33 @@ module Chore
|
|
25
30
|
@@reset_next = true
|
26
31
|
end
|
27
32
|
|
28
|
-
|
33
|
+
private
|
34
|
+
|
35
|
+
# SQS API client object
|
36
|
+
#
|
37
|
+
# @return [Aws::SQS::Client]
|
29
38
|
def sqs
|
30
|
-
@sqs ||=
|
31
|
-
:access_key_id => Chore.config.aws_access_key,
|
32
|
-
:secret_access_key => Chore.config.aws_secret_key,
|
33
|
-
:logger => Chore.logger,
|
34
|
-
:log_level => :debug)
|
39
|
+
@sqs ||= Chore::Queues::SQS.sqs_client
|
35
40
|
end
|
36
41
|
|
37
|
-
# Retrieves the SQS queue
|
42
|
+
# Retrieves the SQS queue object. The method will cache the results to prevent round trips on subsequent calls
|
43
|
+
#
|
38
44
|
# If <tt>reset_connection!</tt> has been called, this will result in the connection being re-initialized,
|
39
45
|
# as well as clear any cached results from prior calls
|
46
|
+
#
|
47
|
+
# @param [String] name Name of SQS queue
|
48
|
+
#
|
49
|
+
# @return [Aws::SQS::Queue]
|
40
50
|
def queue(name)
|
41
|
-
|
42
|
-
|
43
|
-
p.empty!
|
44
|
-
end
|
51
|
+
if @@reset_next
|
52
|
+
Aws.empty_connection_pools!
|
45
53
|
@sqs = nil
|
46
54
|
@@reset_next = false
|
47
55
|
@sqs_queues = {}
|
48
56
|
end
|
49
|
-
|
50
|
-
@
|
57
|
+
|
58
|
+
@sqs_queue_urls[name] ||= sqs.get_queue_url(queue_name: name).queue_url
|
59
|
+
@sqs_queues[name] ||= Aws::SQS::Queue.new(url: @sqs_queue_urls[name], client: sqs)
|
51
60
|
end
|
52
61
|
end
|
53
62
|
end
|
@@ -11,29 +11,29 @@ module Chore
|
|
11
11
|
@size = size
|
12
12
|
@batch = []
|
13
13
|
@mutex = Mutex.new
|
14
|
-
@last_message = nil
|
15
14
|
@callback = nil
|
16
15
|
@running = true
|
17
16
|
end
|
18
17
|
|
19
|
-
# The main entry point of the Batcher, <tt>schedule</tt> begins a thread with the provided +batch_timeout+
|
20
|
-
# as the only argument. While the Batcher is running, it will attempt to check if either the batch is full,
|
21
|
-
# or if the +batch_timeout+ has elapsed since the
|
22
|
-
#
|
23
|
-
#
|
18
|
+
# The main entry point of the Batcher, <tt>schedule</tt> begins a thread with the provided +batch_timeout+
|
19
|
+
# as the only argument. While the Batcher is running, it will attempt to check if either the batch is full,
|
20
|
+
# or if the +batch_timeout+ has elapsed since the oldest message was added. If either case is true, the
|
21
|
+
# items in the batch will be executed.
|
22
|
+
#
|
24
23
|
# Calling <tt>stop</tt> will cause the thread to finish it's current check, and exit
|
25
|
-
def schedule(batch_timeout
|
24
|
+
def schedule(batch_timeout)
|
26
25
|
@thread = Thread.new(batch_timeout) do |timeout|
|
27
|
-
Chore.logger.info "Batching
|
26
|
+
Chore.logger.info "Batching thread starting with #{batch_timeout} second timeout"
|
28
27
|
while @running do
|
29
|
-
begin
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
begin
|
29
|
+
oldest_item = @batch.first
|
30
|
+
timestamp = oldest_item && oldest_item.created_at
|
31
|
+
Chore.logger.debug "Oldest message in batch: #{timestamp}, size: #{@batch.size}"
|
32
|
+
if timestamp && Time.now > (timestamp + timeout)
|
33
|
+
Chore.logger.debug "Batching timeout reached (#{timestamp + timeout}), current size: #{@batch.size}"
|
33
34
|
self.execute(true)
|
34
|
-
@last_message = nil
|
35
35
|
end
|
36
|
-
sleep(1)
|
36
|
+
sleep(1)
|
37
37
|
rescue => e
|
38
38
|
Chore.logger.error "Batcher#schedule raised an exception: #{e.inspect}"
|
39
39
|
end
|
@@ -44,7 +44,6 @@ module Chore
|
|
44
44
|
# Adds the +item+ to the current batch
|
45
45
|
def add(item)
|
46
46
|
@batch << item
|
47
|
-
@last_message = Time.now
|
48
47
|
execute if ready?
|
49
48
|
end
|
50
49
|
|
@@ -10,16 +10,16 @@ module Chore
|
|
10
10
|
end
|
11
11
|
|
12
12
|
# Begins fetching from the configured queue by way of the configured Consumer. This can only be used if you have a
|
13
|
-
# single queue which can be kept up with at a relatively low volume. If you have more than a single queue
|
14
|
-
# it will raise an exception.
|
13
|
+
# single queue which can be kept up with at a relatively low volume. If you have more than a single queue
|
14
|
+
# configured, it will raise an exception.
|
15
15
|
def fetch
|
16
16
|
Chore.logger.debug "Starting up consumer strategy: #{self.class.name}"
|
17
17
|
queues = Chore.config.queues
|
18
18
|
raise "When using SingleConsumerStrategy only one queue can be defined. Queues: #{queues}" unless queues.size == 1
|
19
|
-
|
19
|
+
|
20
20
|
@consumer = Chore.config.consumer.new(queues.first)
|
21
|
-
@consumer.consume do |
|
22
|
-
work = UnitOfWork.new(
|
21
|
+
@consumer.consume do |message_id, message_receipt_handle, queue_name, queue_timeout, body, previous_attempts|
|
22
|
+
work = UnitOfWork.new(message_id, message_receipt_handle, queue_name, queue_timeout, body, previous_attempts, @consumer)
|
23
23
|
@fetcher.manager.assign(work)
|
24
24
|
end
|
25
25
|
end
|
@@ -5,13 +5,14 @@ module Chore
|
|
5
5
|
attr_accessor :batcher
|
6
6
|
|
7
7
|
Chore::CLI.register_option 'batch_size', '--batch-size SIZE', Integer, 'Number of items to collect for a single worker to process'
|
8
|
+
Chore::CLI.register_option 'batch_timeout', '--batch-timeout SIZE', Integer, 'Maximum number of seconds to wait until processing a message'
|
8
9
|
Chore::CLI.register_option 'threads_per_queue', '--threads-per-queue NUM_THREADS', Integer, 'Number of threads to create for each named queue'
|
9
10
|
|
10
11
|
def initialize(fetcher)
|
11
12
|
@fetcher = fetcher
|
12
13
|
@batcher = Batcher.new(Chore.config.batch_size)
|
13
14
|
@batcher.callback = lambda { |batch| @fetcher.manager.assign(batch) }
|
14
|
-
@batcher.schedule
|
15
|
+
@batcher.schedule(Chore.config.batch_timeout)
|
15
16
|
@running = true
|
16
17
|
end
|
17
18
|
|
@@ -22,7 +23,7 @@ module Chore
|
|
22
23
|
Chore.logger.debug "Starting up consumer strategy: #{self.class.name}"
|
23
24
|
threads = []
|
24
25
|
Chore.config.queues.each do |queue|
|
25
|
-
Chore.config.threads_per_queue.times do
|
26
|
+
Chore.config.threads_per_queue.times do
|
26
27
|
if running?
|
27
28
|
threads << start_consumer_thread(queue)
|
28
29
|
end
|
@@ -31,7 +32,7 @@ module Chore
|
|
31
32
|
|
32
33
|
threads.each(&:join)
|
33
34
|
end
|
34
|
-
|
35
|
+
|
35
36
|
# If the ThreadedConsumerStrategy is currently running <tt>stop!</tt> will begin signalling it to stop
|
36
37
|
# It will stop the batcher from forking more work, as well as set a flag which will disable it's own consuming
|
37
38
|
# threads once they finish with their current work.
|
@@ -48,21 +49,22 @@ module Chore
|
|
48
49
|
@running
|
49
50
|
end
|
50
51
|
|
51
|
-
private
|
52
|
+
private
|
52
53
|
# Starts a consumer thread for polling the given +queue+.
|
53
54
|
# If <tt>stop!<tt> is called, the threads will shut themsevles down.
|
54
55
|
def start_consumer_thread(queue)
|
55
56
|
t = Thread.new(queue) do |tQueue|
|
56
57
|
begin
|
57
58
|
consumer = Chore.config.consumer.new(tQueue)
|
58
|
-
consumer.consume do |
|
59
|
+
consumer.consume do |message_id, message_receipt_handle, queue_name, queue_timeout, body, previous_attempts|
|
59
60
|
# Quick hack to force this thread to end it's work
|
60
61
|
# if we're shutting down. Could be delayed due to the
|
61
62
|
# weird sometimes-blocking nature of SQS.
|
62
63
|
consumer.stop if !running?
|
63
|
-
Chore.logger.debug { "Got message: #{
|
64
|
+
Chore.logger.debug { "Got message: #{message_id}"}
|
64
65
|
|
65
|
-
work = UnitOfWork.new(
|
66
|
+
work = UnitOfWork.new(message_id, message_receipt_handle, queue_name, queue_timeout, body, previous_attempts, consumer)
|
67
|
+
Chore.run_hooks_for(:consumed_from_source, work)
|
66
68
|
@batcher.add(work)
|
67
69
|
end
|
68
70
|
rescue Chore::TerribleMistake
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Chore
|
2
|
+
module Strategy
|
3
|
+
class ThrottledConsumerStrategy #:nodoc:
|
4
|
+
def initialize(fetcher)
|
5
|
+
@fetcher = fetcher
|
6
|
+
@queue = SizedQueue.new(Chore.config.num_workers)
|
7
|
+
@return_queue = Queue.new
|
8
|
+
@max_queue_size = Chore.config.num_workers
|
9
|
+
@consumers_per_queue = Chore.config.threads_per_queue
|
10
|
+
@running = true
|
11
|
+
@consumers = []
|
12
|
+
end
|
13
|
+
|
14
|
+
# Begins fetching from queues by spinning up the configured
|
15
|
+
# +:threads_per_queue:+ count of threads for each
|
16
|
+
# queue you're consuming from.
|
17
|
+
# Once all threads are spun up and running, the threads are then joined.
|
18
|
+
|
19
|
+
def fetch
|
20
|
+
Chore.logger.info "TCS: Starting up: #{self.class.name}"
|
21
|
+
threads = []
|
22
|
+
Chore.config.queues.each do |consume_queue|
|
23
|
+
Chore.logger.info "TCS: Starting #{@consumers_per_queue} threads for Queue #{consume_queue}"
|
24
|
+
@consumers_per_queue.times do
|
25
|
+
next unless running?
|
26
|
+
threads << consume(consume_queue)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
threads.each(&:join)
|
30
|
+
end
|
31
|
+
|
32
|
+
# If the ThreadedConsumerStrategy is currently running <tt>stop!</tt>
|
33
|
+
# will begin signalling it to stop. It will stop the batcher
|
34
|
+
# from forking more work,as well as set a flag which will disable
|
35
|
+
# it's own consuming threads once they finish with their current work.
|
36
|
+
def stop!
|
37
|
+
if running?
|
38
|
+
Chore.logger.info "TCS: Shutting down fetcher: #{self.class.name}"
|
39
|
+
@running = false
|
40
|
+
@consumers.each do |consumer|
|
41
|
+
Chore.logger.info "TCS: Stopping consumer: #{consumer.object_id}"
|
42
|
+
@queue.clear
|
43
|
+
@return_queue.clear
|
44
|
+
consumer.stop
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns whether or not the ThreadedConsumerStrategy is running or not
|
50
|
+
def running?
|
51
|
+
@running
|
52
|
+
end
|
53
|
+
|
54
|
+
# return upto number_of_free_workers work objects
|
55
|
+
def provide_work(no_free_workers)
|
56
|
+
work_units = []
|
57
|
+
free_workers = [no_free_workers, @queue.size + @return_queue.size].min
|
58
|
+
while free_workers > 0
|
59
|
+
# Drain from the return queue first, then the consumer thread queue
|
60
|
+
queue = @return_queue.empty? ? @queue : @return_queue
|
61
|
+
work_units << queue.pop
|
62
|
+
free_workers -= 1
|
63
|
+
end
|
64
|
+
work_units
|
65
|
+
end
|
66
|
+
|
67
|
+
# Gives work back to the queue in case it couldn't be assigned
|
68
|
+
#
|
69
|
+
# This will go into a separate queue so that it will be prioritized
|
70
|
+
# over other work that hasn't been attempted yet. It also avoids
|
71
|
+
# a deadlock where @queue is full and the master is waiting to return
|
72
|
+
# work that it couldn't assign.
|
73
|
+
def return_work(work_units)
|
74
|
+
work_units.each do |work|
|
75
|
+
@return_queue.push(work)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def consume(consume_queue)
|
82
|
+
consumer = Chore.config.consumer.new(consume_queue)
|
83
|
+
@consumers << consumer
|
84
|
+
start_consumer_thread(consumer)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Starts a consumer thread for polling the given +consume_queue+.
|
88
|
+
# If <tt>stop!<tt> is called, the threads will shut themsevles down.
|
89
|
+
def start_consumer_thread(consumer)
|
90
|
+
t = Thread.new(consumer) do |th|
|
91
|
+
begin
|
92
|
+
create_work_units(th)
|
93
|
+
rescue Chore::TerribleMistake => e
|
94
|
+
Chore.logger.error 'Terrible mistake, shutting down Chore'
|
95
|
+
Chore.logger.error "#{e.inspect} at #{e.backtrace}"
|
96
|
+
@fetcher.manager.shutdown!
|
97
|
+
end
|
98
|
+
end
|
99
|
+
t
|
100
|
+
end
|
101
|
+
|
102
|
+
def create_work_units(consumer)
|
103
|
+
consumer.consume do |message_id, message_receipt_handle, queue, timeout, body, previous_attempts|
|
104
|
+
# Note: The unit of work object contains a consumer object that when
|
105
|
+
# used to consume from SQS, would have a mutex (that comes as a part
|
106
|
+
# of the AWS sdk); When sending these objects across from one process
|
107
|
+
# to another, we cannot send this across (becasue of the mutex). To
|
108
|
+
# work around this, we simply ignore the consumer object when creating
|
109
|
+
# the unit of work object, and when the worker recieves the work
|
110
|
+
# object, it assigns it a consumer object.
|
111
|
+
# (to allow for communication back to the queue it was consumed from)
|
112
|
+
work = UnitOfWork.new(message_id, message_receipt_handle, queue, timeout, body, previous_attempts)
|
113
|
+
Chore.run_hooks_for(:consumed_from_source, work)
|
114
|
+
@queue.push(work) if running?
|
115
|
+
Chore.run_hooks_for(:added_to_queue, work)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end # ThrottledConsumerStrategy
|
119
|
+
end
|
120
|
+
end # Chore
|
@@ -3,6 +3,7 @@ require 'chore/signal'
|
|
3
3
|
module Chore
|
4
4
|
module Strategy
|
5
5
|
class ForkedWorkerStrategy #:nodoc:
|
6
|
+
include Util
|
6
7
|
attr_accessor :workers
|
7
8
|
|
8
9
|
def initialize(manager, opts={})
|
@@ -63,6 +64,9 @@ module Chore
|
|
63
64
|
pid = nil
|
64
65
|
Chore.run_hooks_for(:around_fork,w) do
|
65
66
|
pid = fork do
|
67
|
+
work.each do | item |
|
68
|
+
Chore.run_hooks_for(:fetched_off_internal_q, item)
|
69
|
+
end
|
66
70
|
after_fork(w)
|
67
71
|
Chore.run_hooks_for(:within_fork,w) do
|
68
72
|
Chore.run_hooks_for(:after_fork,w)
|
@@ -132,7 +136,7 @@ module Chore
|
|
132
136
|
def after_fork(worker)
|
133
137
|
# Immediately swap out the process name so that it doesn't look like
|
134
138
|
# the master process
|
135
|
-
procline("Started:#{Time.now}")
|
139
|
+
procline("#{Chore.config.worker_procline}:Started:#{Time.now}")
|
136
140
|
|
137
141
|
clear_child_signals
|
138
142
|
trap_child_signals(worker)
|
@@ -202,11 +206,6 @@ module Chore
|
|
202
206
|
Kernel.fork(&block)
|
203
207
|
end
|
204
208
|
|
205
|
-
def procline(str)
|
206
|
-
Chore.logger.info str
|
207
|
-
$0 = "chore-#{Chore::VERSION}:#{str}"
|
208
|
-
end
|
209
|
-
|
210
209
|
def signal_children(sig, pids_to_signal = pids)
|
211
210
|
pids_to_signal.each do |pid|
|
212
211
|
begin
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Chore
|
4
|
+
module Strategy
|
5
|
+
module Ipc #:nodoc:
|
6
|
+
BIG_ENDIAN = 'L>'.freeze
|
7
|
+
MSG_BYTES = 4
|
8
|
+
READY_MSG = 'R'
|
9
|
+
|
10
|
+
def create_master_socket
|
11
|
+
File.delete socket_file if File.exist? socket_file
|
12
|
+
UNIXServer.new(socket_file).tap do |socket|
|
13
|
+
socket_options(socket)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def child_connection(socket)
|
18
|
+
socket.accept
|
19
|
+
end
|
20
|
+
|
21
|
+
# Sending a message to a socket (must be a connected socket)
|
22
|
+
def send_msg(socket, msg)
|
23
|
+
raise 'send_msg cannot send empty messages' if msg.nil? || msg.size.zero?
|
24
|
+
message = Marshal.dump(msg)
|
25
|
+
encoded_size = [message.size].pack(BIG_ENDIAN)
|
26
|
+
encoded_message = "#{encoded_size}#{message}"
|
27
|
+
socket.send encoded_message, 0
|
28
|
+
end
|
29
|
+
|
30
|
+
# read a message from socket (must be a connected socket)
|
31
|
+
def read_msg(socket)
|
32
|
+
encoded_size = socket.recv(MSG_BYTES, Socket::MSG_PEEK)
|
33
|
+
return if encoded_size.nil? || encoded_size == ''
|
34
|
+
|
35
|
+
size = encoded_size.unpack(BIG_ENDIAN).first
|
36
|
+
encoded_message = socket.recv(MSG_BYTES + size)
|
37
|
+
Marshal.load(encoded_message[MSG_BYTES..-1])
|
38
|
+
rescue Errno::ECONNRESET => ex
|
39
|
+
Chore.logger.info "IPC: Connection was closed on socket #{socket}"
|
40
|
+
raise ex
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_worker_socket
|
44
|
+
UNIXSocket.new(socket_file).tap do |socket|
|
45
|
+
socket_options(socket)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def clear_ready(socket)
|
50
|
+
_ = socket.gets
|
51
|
+
end
|
52
|
+
|
53
|
+
def signal_ready(socket)
|
54
|
+
socket.puts READY_MSG
|
55
|
+
rescue Errno::EPIPE => ex
|
56
|
+
Chore.logger.info 'IPC: Connection was shutdown by master'
|
57
|
+
raise ex
|
58
|
+
end
|
59
|
+
|
60
|
+
def select_sockets(sockets, self_pipe = nil, timeout = 0.5)
|
61
|
+
all_socks = [sockets, self_pipe].flatten.compact
|
62
|
+
IO.select(all_socks, nil, all_socks, timeout)
|
63
|
+
end
|
64
|
+
|
65
|
+
def delete_socket_file
|
66
|
+
File.unlink(socket_file)
|
67
|
+
rescue
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# Used for unit tests
|
72
|
+
def ipc_help
|
73
|
+
:available
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def socket_file
|
79
|
+
"./prefork_worker_sock-#{Process.pid}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def socket_options(socket)
|
83
|
+
socket.setsockopt(:SOCKET, :REUSEADDR, true)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|