sqewer 5.0.0 → 5.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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/ACTIVE_JOB.md +64 -0
- data/FAQ.md +0 -4
- data/Gemfile +4 -0
- data/README.md +4 -0
- data/Rakefile +1 -0
- data/bin/sqewer +7 -0
- data/bin/sqewer_rails +10 -0
- data/lib/sqewer.rb +18 -1
- data/lib/sqewer/atomic_counter.rb +2 -2
- data/lib/sqewer/cli.rb +3 -3
- data/lib/sqewer/connection.rb +16 -16
- data/lib/sqewer/connection_messagebox.rb +4 -4
- data/lib/sqewer/execution_context.rb +5 -5
- data/lib/sqewer/extensions/active_job_adapter.rb +78 -0
- data/lib/sqewer/{contrib → extensions}/appsignal_wrapper.rb +4 -4
- data/lib/sqewer/extensions/railtie.rb +12 -0
- data/lib/sqewer/middleware_stack.rb +7 -7
- data/lib/sqewer/null_logger.rb +1 -1
- data/lib/sqewer/resubmit.rb +17 -0
- data/lib/sqewer/serializer.rb +25 -25
- data/lib/sqewer/simple_job.rb +11 -11
- data/lib/sqewer/state_lock.rb +2 -2
- data/lib/sqewer/submitter.rb +17 -3
- data/lib/sqewer/version.rb +1 -1
- data/lib/sqewer/worker.rb +47 -47
- data/spec/spec_helper.rb +8 -2
- data/spec/sqewer/active_job_spec.rb +113 -0
- data/spec/sqewer/cli_spec.rb +48 -31
- data/spec/sqewer/serializer_spec.rb +51 -56
- data/spec/sqewer/submitter_spec.rb +18 -0
- data/spec/sqewer/worker_spec.rb +11 -9
- data/sqewer.gemspec +21 -5
- metadata +55 -5
- data/lib/sqewer/contrib/performable.rb +0 -23
@@ -7,19 +7,19 @@ module Sqewer
|
|
7
7
|
# Unserialize the job
|
8
8
|
def around_deserialization(serializer, msg_id, msg_payload)
|
9
9
|
return yield unless (defined?(Appsignal) && Appsignal.active?)
|
10
|
-
|
10
|
+
|
11
11
|
Appsignal.monitor_transaction('perform_job.demarshal',
|
12
12
|
:class => serializer.class.to_s, :params => {:recepit_handle => msg_id}, :method => 'deserialize') do
|
13
13
|
yield
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
# Run the job with Appsignal monitoring.
|
18
18
|
def around_execution(job, context)
|
19
19
|
return yield unless (defined?(Appsignal) && Appsignal.active?)
|
20
|
-
|
20
|
+
job_params = job.respond_to?(:to_h) ? job.to_h : {}
|
21
21
|
Appsignal.monitor_transaction('perform_job.sqewer',
|
22
|
-
:class => job.class.to_s, :params =>
|
22
|
+
:class => job.class.to_s, :params => job_params, :method => 'run') do |t|
|
23
23
|
context['appsignal.transaction'] = t
|
24
24
|
yield
|
25
25
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Sqewer
|
2
|
+
require 'sqewer/extensions/active_job_adapter' if defined?(::ActiveJob)
|
3
|
+
|
4
|
+
# Loads the Sqewer components that provide ActiveJob compatibility
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
initializer "sqewer.load_active_job_adapter" do |app|
|
7
|
+
if defined?(::ActiveJob)
|
8
|
+
Rails.logger.warn "sqewer set as ActiveJob adapter. Make sure to call 'Rails.application.eager_load!` in your worker process"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -1,18 +1,18 @@
|
|
1
1
|
# Allows arbitrary wrapping of the job deserialization and job execution procedures
|
2
2
|
class Sqewer::MiddlewareStack
|
3
|
-
|
3
|
+
|
4
4
|
# Returns the default middleware stack, which is empty (an instance of None).
|
5
5
|
#
|
6
6
|
# @return [MiddlewareStack] the default empty stack
|
7
7
|
def self.default
|
8
8
|
@instance ||= new
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
# Creates a new MiddlewareStack. Once created, handlers can be added using `:<<`
|
12
12
|
def initialize
|
13
13
|
@handlers = []
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
# Adds a handler. The handler should respond to :around_deserialization and #around_execution.
|
17
17
|
#
|
18
18
|
# @param handler[#around_deserializarion, #around_execution] The middleware item to insert
|
@@ -21,13 +21,13 @@ class Sqewer::MiddlewareStack
|
|
21
21
|
@handlers << handler
|
22
22
|
# TODO: cache the wrapping proc
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def around_execution(job, context, &inner_block)
|
26
26
|
return yield if @handlers.empty?
|
27
|
-
|
27
|
+
|
28
28
|
responders = @handlers.select{|e| e.respond_to?(:around_execution) }
|
29
29
|
responders.reverse.inject(inner_block) {|outer_block, middleware_object|
|
30
|
-
->{
|
30
|
+
->{
|
31
31
|
middleware_object.public_send(:around_execution, job, context, &outer_block)
|
32
32
|
}
|
33
33
|
}.call
|
@@ -35,7 +35,7 @@ class Sqewer::MiddlewareStack
|
|
35
35
|
|
36
36
|
def around_deserialization(serializer, message_id, message_body, &inner_block)
|
37
37
|
return yield if @handlers.empty?
|
38
|
-
|
38
|
+
|
39
39
|
responders = @handlers.select{|e| e.respond_to?(:around_deserialization) }
|
40
40
|
responders.reverse.inject(inner_block) {|outer_block, middleware_object|
|
41
41
|
->{ middleware_object.public_send(:around_deserialization, serializer, message_id, message_body, &outer_block) }
|
data/lib/sqewer/null_logger.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Sqewer
|
2
|
+
class Resubmit
|
3
|
+
attr_reader :job
|
4
|
+
attr_reader :execute_after
|
5
|
+
|
6
|
+
def initialize(job_to_resubmit, execute_after_timestamp)
|
7
|
+
@job = job_to_resubmit
|
8
|
+
@execute_after = execute_after_timestamp
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(ctx)
|
12
|
+
# Take the maximum delay period SQS allows
|
13
|
+
required_delay = (@execute_after - Time.now.to_i)
|
14
|
+
ctx.submit!(@job, delay_seconds: required_delay)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/sqewer/serializer.rb
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
# custom job objects from S3 bucket notifications, you might want to override this
|
5
5
|
# class and feed the overridden instance to {Sqewer::Worker}.
|
6
6
|
class Sqewer::Serializer
|
7
|
-
|
7
|
+
|
8
8
|
# Returns the default Serializer, of which we store one instance
|
9
9
|
# (because the serializer is stateless).
|
10
10
|
#
|
@@ -12,10 +12,9 @@ class Sqewer::Serializer
|
|
12
12
|
def self.default
|
13
13
|
@instance ||= new
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
AnonymousJobClass = Class.new(StandardError)
|
17
|
-
|
18
|
-
|
17
|
+
|
19
18
|
# Instantiate a Job object from a message body string. If the
|
20
19
|
# returned result is `nil`, the job will be skipped.
|
21
20
|
#
|
@@ -24,48 +23,49 @@ class Sqewer::Serializer
|
|
24
23
|
def unserialize(message_body)
|
25
24
|
job_ticket_hash = JSON.parse(message_body, symbolize_names: true)
|
26
25
|
raise "Job ticket must unmarshal into a Hash" unless job_ticket_hash.is_a?(Hash)
|
27
|
-
|
28
|
-
job_ticket_hash = convert_old_ticket_format(job_ticket_hash) if job_ticket_hash[:job_class]
|
29
|
-
|
26
|
+
|
30
27
|
# Use fetch() to raise a descriptive KeyError if none
|
31
28
|
job_class_name = job_ticket_hash.delete(:_job_class)
|
32
29
|
raise ":_job_class not set in the ticket" unless job_class_name
|
33
30
|
job_class = Kernel.const_get(job_class_name)
|
31
|
+
|
32
|
+
# Grab the parameter that is responsible for executing the job later. If it is not set,
|
33
|
+
# use a default that will put us ahead of that execution deadline from the start.
|
34
|
+
t = Time.now.to_i
|
35
|
+
execute_after = job_ticket_hash.fetch(:_execute_after) { t - 5 }
|
34
36
|
|
35
37
|
job_params = job_ticket_hash.delete(:_job_params)
|
36
|
-
if job_params.nil? || job_params.empty?
|
38
|
+
job = if job_params.nil? || job_params.empty?
|
37
39
|
job_class.new # no args
|
38
40
|
else
|
39
|
-
|
40
|
-
job_class.new(**job_params) # The rest of the message are keyword arguments for the job
|
41
|
-
rescue ArgumentError => e
|
42
|
-
raise ArityMismatch, "Could not instantiate #{job_class} because it did not accept the arguments #{job_params.inspect}"
|
43
|
-
end
|
41
|
+
job_class.new(**job_params) # The rest of the message are keyword arguments for the job
|
44
42
|
end
|
43
|
+
|
44
|
+
# If the job is not up for execution now, wrap it with something that will
|
45
|
+
# re-submit it for later execution when the run() method is called
|
46
|
+
return ::Sqewer::Resubmit.new(job, execute_after) if execute_after > t
|
47
|
+
|
48
|
+
job
|
45
49
|
end
|
46
|
-
|
50
|
+
|
47
51
|
# Converts the given Job into a string, which can be submitted to the queue
|
48
52
|
#
|
49
53
|
# @param job[#to_h] an object that supports `to_h`
|
54
|
+
# @param execute_after_timestamp[#to_i, nil] the Unix timestamp after which the job may be executed
|
50
55
|
# @return [String] serialized string ready to be put into the queue
|
51
|
-
def serialize(job)
|
56
|
+
def serialize(job, execute_after_timestamp = nil)
|
52
57
|
job_class_name = job.class.to_s
|
53
|
-
|
58
|
+
|
54
59
|
begin
|
55
60
|
Kernel.const_get(job_class_name)
|
56
61
|
rescue NameError
|
57
62
|
raise AnonymousJobClass, "The class of #{job.inspect} could not be resolved and will not restore to a Job"
|
58
63
|
end
|
59
|
-
|
64
|
+
|
60
65
|
job_params = job.respond_to?(:to_h) ? job.to_h : nil
|
61
66
|
job_ticket_hash = {_job_class: job_class_name, _job_params: job_params}
|
67
|
+
job_ticket_hash[:_execute_after] = execute_after_timestamp.to_i if execute_after_timestamp
|
68
|
+
|
62
69
|
JSON.dump(job_ticket_hash)
|
63
70
|
end
|
64
|
-
|
65
|
-
private
|
66
|
-
|
67
|
-
def convert_old_ticket_format(hash_of_properties)
|
68
|
-
job_class = hash_of_properties.delete(:job_class)
|
69
|
-
{_job_class: job_class, _job_params: hash_of_properties}
|
70
|
-
end
|
71
|
-
end
|
71
|
+
end
|
data/lib/sqewer/simple_job.rb
CHANGED
@@ -7,19 +7,19 @@
|
|
7
7
|
module Sqewer::SimpleJob
|
8
8
|
UnknownJobAttribute = Class.new(StandardError)
|
9
9
|
MissingAttribute = Class.new(StandardError)
|
10
|
-
|
10
|
+
|
11
11
|
EQ_END = /(\w+)(\=)$/
|
12
|
-
|
12
|
+
|
13
13
|
# Returns the list of methods on the object that have corresponding accessors.
|
14
14
|
# This is then used by #inspect to compose a list of the job parameters, formatted
|
15
15
|
# as an inspected Hash.
|
16
16
|
#
|
17
|
-
# @return [Array<Symbol>] the array of attributes to show via inspect
|
17
|
+
# @return [Array<Symbol>] the array of attributes to show via inspect
|
18
18
|
def inspectable_attributes
|
19
19
|
# All the attributes that have accessors
|
20
20
|
methods.grep(EQ_END).map{|e| e.to_s.gsub(EQ_END, '\1')}.map(&:to_sym)
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
# Returns the inspection string with the job and all of it's instantiation keyword attributes.
|
24
24
|
# If `inspectable_attributes` has been overridden, the attributes returned by that method will be the
|
25
25
|
# ones returned in the inspection string.
|
@@ -36,7 +36,7 @@ module Sqewer::SimpleJob
|
|
36
36
|
end
|
37
37
|
"<#{self.class}:#{h.inspect}>"
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
# Initializes a new Job with the given job args. Will check for presence of
|
41
41
|
# accessor methods for each of the arguments, and call them with the arguments given.
|
42
42
|
#
|
@@ -49,30 +49,30 @@ module Sqewer::SimpleJob
|
|
49
49
|
@simple_job_args = jobargs.keys
|
50
50
|
touched_attributes = Set.new
|
51
51
|
jobargs.each do |(k,v)|
|
52
|
-
|
52
|
+
|
53
53
|
accessor = "#{k}="
|
54
54
|
touched_attributes << k
|
55
55
|
unless respond_to?(accessor)
|
56
56
|
raise UnknownJobAttribute, "Unknown attribute #{k.inspect} for #{self.class}"
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
send("#{k}=", v)
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
accessors = methods.grep(EQ_END).map{|method_name| method_name.to_s.gsub(EQ_END, '\1').to_sym }
|
63
63
|
settable_attributes = Set.new(accessors)
|
64
64
|
missing_attributes = settable_attributes - touched_attributes
|
65
|
-
|
65
|
+
|
66
66
|
missing_attributes.each do | attr |
|
67
67
|
raise MissingAttribute, "Missing job attribute #{attr.inspect}"
|
68
68
|
end
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
def to_h
|
72
72
|
keys_and_values = @simple_job_args.each_with_object({}) do |k, h|
|
73
73
|
h[k] = send(k)
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
keys_and_values
|
77
77
|
end
|
78
78
|
end
|
data/lib/sqewer/state_lock.rb
CHANGED
@@ -13,11 +13,11 @@ class Sqewer::StateLock < SimpleDelegator
|
|
13
13
|
m.permit_transition :starting => :failed # Failed to start
|
14
14
|
__setobj__(m)
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def in_state?(some_state)
|
18
18
|
@m.synchronize { __getobj__.in_state?(some_state) }
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def transition!(to_state)
|
22
22
|
@m.synchronize { __getobj__.transition!(to_state) }
|
23
23
|
end
|
data/lib/sqewer/submitter.rb
CHANGED
@@ -3,14 +3,28 @@
|
|
3
3
|
# and the serializer (something that responds to `#serialize`) to
|
4
4
|
# convert the job into the string that will be put in the queue.
|
5
5
|
class Sqewer::Submitter < Struct.new(:connection, :serializer)
|
6
|
-
|
6
|
+
|
7
7
|
# Returns a default Submitter, configured with the default connection
|
8
8
|
# and the default serializer.
|
9
9
|
def self.default
|
10
10
|
new(Sqewer::Connection.default, Sqewer::Serializer.default)
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
def submit!(job, **kwargs_for_send)
|
14
|
-
|
14
|
+
message_body = if delay_by_seconds = kwargs_for_send[:delay_seconds]
|
15
|
+
clamped_delay = clamp_delay(delay_by_seconds)
|
16
|
+
kwargs_for_send[:delay_seconds] = clamped_delay
|
17
|
+
# Pass the actual delay value to the serializer, to be stored in executed_at
|
18
|
+
serializer.serialize(job, Time.now.to_i + delay_by_seconds)
|
19
|
+
else
|
20
|
+
serializer.serialize(job)
|
21
|
+
end
|
22
|
+
connection.send_message(message_body, **kwargs_for_send)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def clamp_delay(delay)
|
28
|
+
[1, 899, delay].sort[1]
|
15
29
|
end
|
16
30
|
end
|
data/lib/sqewer/version.rb
CHANGED
data/lib/sqewer/worker.rb
CHANGED
@@ -8,38 +8,38 @@ class Sqewer::Worker
|
|
8
8
|
DEFAULT_NUM_THREADS = 4
|
9
9
|
SLEEP_SECONDS_ON_EMPTY_QUEUE = 1
|
10
10
|
THROTTLE_FACTOR = 2
|
11
|
-
|
11
|
+
|
12
12
|
# @return [Logger] The logger used for job execution
|
13
13
|
attr_reader :logger
|
14
|
-
|
14
|
+
|
15
15
|
# @return [Sqewer::Connection] The connection for sending and receiving messages
|
16
16
|
attr_reader :connection
|
17
|
-
|
17
|
+
|
18
18
|
# @return [Sqewer::Serializer] The serializer for unmarshalling and marshalling
|
19
19
|
attr_reader :serializer
|
20
|
-
|
20
|
+
|
21
21
|
# @return [Sqewer::MiddlewareStack] The stack used when executing the job
|
22
22
|
attr_reader :middleware_stack
|
23
|
-
|
23
|
+
|
24
24
|
# @return [Class] The class to use when instantiating the execution context
|
25
25
|
attr_reader :execution_context_class
|
26
|
-
|
26
|
+
|
27
27
|
# @return [Class] The class used to create the Submitter used by jobs to spawn other jobs
|
28
28
|
attr_reader :submitter_class
|
29
|
-
|
29
|
+
|
30
30
|
# @return [Array<Thread>] all the currently running threads of the Worker
|
31
31
|
attr_reader :threads
|
32
|
-
|
32
|
+
|
33
33
|
# @return [Fixnum] the number of worker threads set up for this Worker
|
34
34
|
attr_reader :num_threads
|
35
|
-
|
36
|
-
# Returns
|
35
|
+
|
36
|
+
# Returns a Worker instance, configured based on the default components
|
37
37
|
#
|
38
38
|
# @return [Sqewer::Worker]
|
39
39
|
def self.default
|
40
|
-
|
40
|
+
new
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
# Creates a new Worker. The Worker, unlike it is in the Rails tradition, is only responsible for
|
44
44
|
# the actual processing of jobs, and not for the job arguments.
|
45
45
|
#
|
@@ -58,7 +58,7 @@ class Sqewer::Worker
|
|
58
58
|
middleware_stack: Sqewer::MiddlewareStack.default,
|
59
59
|
logger: Logger.new($stderr),
|
60
60
|
num_threads: DEFAULT_NUM_THREADS)
|
61
|
-
|
61
|
+
|
62
62
|
@logger = logger
|
63
63
|
@connection = connection
|
64
64
|
@serializer = serializer
|
@@ -66,38 +66,38 @@ class Sqewer::Worker
|
|
66
66
|
@execution_context_class = execution_context_class
|
67
67
|
@submitter_class = submitter_class
|
68
68
|
@num_threads = num_threads
|
69
|
-
|
69
|
+
|
70
70
|
@threads = []
|
71
|
-
|
71
|
+
|
72
72
|
raise ArgumentError, "num_threads must be > 0" unless num_threads > 0
|
73
|
-
|
73
|
+
|
74
74
|
@execution_counter = Sqewer::AtomicCounter.new
|
75
|
-
|
75
|
+
|
76
76
|
@state = Sqewer::StateLock.new
|
77
77
|
end
|
78
|
-
|
78
|
+
|
79
79
|
# Start listening on the queue, spin up a number of consumer threads that will execute the jobs.
|
80
80
|
#
|
81
81
|
# @param num_threads[Fixnum] the number of consumer/executor threads to spin up
|
82
82
|
# @return [void]
|
83
83
|
def start
|
84
84
|
@state.transition! :starting
|
85
|
-
|
85
|
+
|
86
86
|
@logger.info { '[worker] Starting with %d consumer threads' % @num_threads }
|
87
87
|
@execution_queue = Queue.new
|
88
|
-
|
88
|
+
|
89
89
|
consumers = (1..@num_threads).map do
|
90
90
|
Thread.new do
|
91
91
|
catch(:goodbye) { loop {take_and_execute} }
|
92
92
|
end
|
93
93
|
end
|
94
|
-
|
94
|
+
|
95
95
|
# Create the provider thread. When the execution queue is exhausted,
|
96
96
|
# grab new messages and place them on the local queue.
|
97
97
|
provider = Thread.new do
|
98
98
|
loop do
|
99
99
|
break if stopping?
|
100
|
-
|
100
|
+
|
101
101
|
if queue_has_capacity?
|
102
102
|
messages = @connection.receive_messages
|
103
103
|
if messages.any?
|
@@ -110,12 +110,12 @@ class Sqewer::Worker
|
|
110
110
|
else
|
111
111
|
@logger.debug { "[worker] Cache is full (%d items), postponing receive" % @execution_queue.length }
|
112
112
|
sleep SLEEP_SECONDS_ON_EMPTY_QUEUE
|
113
|
-
end
|
113
|
+
end
|
114
114
|
end
|
115
115
|
end
|
116
|
-
|
116
|
+
|
117
117
|
@threads = consumers + [provider]
|
118
|
-
|
118
|
+
|
119
119
|
# If any of our threads are already dead, it means there is some misconfiguration and startup failed
|
120
120
|
if @threads.any?{|t| !t.alive? }
|
121
121
|
@threads.map(&:kill)
|
@@ -126,7 +126,7 @@ class Sqewer::Worker
|
|
126
126
|
@logger.info { '[worker] Started, %d consumer threads' % consumers.length }
|
127
127
|
end
|
128
128
|
end
|
129
|
-
|
129
|
+
|
130
130
|
# Attempts to softly stop the running consumers and the producer. Once the call is made,
|
131
131
|
# all the threads will stop after the local cache of messages is emptied. This is to ensure that
|
132
132
|
# message drops do not happen just because the worker is about to be terminated.
|
@@ -140,20 +140,20 @@ class Sqewer::Worker
|
|
140
140
|
loop do
|
141
141
|
n_live = @threads.select(&:alive?).length
|
142
142
|
break if n_live.zero?
|
143
|
-
|
143
|
+
|
144
144
|
n_dead = @threads.length - n_live
|
145
145
|
@logger.info { '[worker] Staged shutdown, %d threads alive, %d have quit, %d jobs in local cache' %
|
146
146
|
[n_live, n_dead, @execution_queue.length] }
|
147
|
-
|
147
|
+
|
148
148
|
sleep 2
|
149
149
|
end
|
150
|
-
|
150
|
+
|
151
151
|
@threads.map(&:join)
|
152
152
|
@logger.info { '[worker] Stopped'}
|
153
153
|
@state.transition! :stopped
|
154
154
|
true
|
155
155
|
end
|
156
|
-
|
156
|
+
|
157
157
|
# Peforms a hard shutdown by killing all the threads
|
158
158
|
def kill
|
159
159
|
@state.transition! :stopping
|
@@ -162,7 +162,7 @@ class Sqewer::Worker
|
|
162
162
|
@logger.info { '[worker] Stopped'}
|
163
163
|
@state.transition! :stopped
|
164
164
|
end
|
165
|
-
|
165
|
+
|
166
166
|
# Prints the status and the backtraces of all controlled threads to the logger
|
167
167
|
def debug_thread_information!
|
168
168
|
@threads.each do | t |
|
@@ -170,48 +170,48 @@ class Sqewer::Worker
|
|
170
170
|
@logger.debug { t.backtrace }
|
171
171
|
end
|
172
172
|
end
|
173
|
-
|
173
|
+
|
174
174
|
private
|
175
|
-
|
175
|
+
|
176
176
|
def stopping?
|
177
177
|
@state.in_state?(:stopping)
|
178
178
|
end
|
179
|
-
|
179
|
+
|
180
180
|
def queue_has_capacity?
|
181
181
|
@execution_queue.length < (@num_threads * THROTTLE_FACTOR)
|
182
182
|
end
|
183
|
-
|
183
|
+
|
184
184
|
def handle_message(message)
|
185
185
|
return unless message.receipt_handle
|
186
|
-
|
186
|
+
|
187
187
|
# Create a messagebox that buffers all the calls to Connection, so that
|
188
188
|
# we can send out those commands in one go (without interfering with senders
|
189
189
|
# on other threads, as it seems the Aws::SQS::Client is not entirely
|
190
190
|
# thread-safe - or at least not it's HTTP client part).
|
191
191
|
box = Sqewer::ConnectionMessagebox.new(connection)
|
192
192
|
return box.delete_message(message.receipt_handle) unless message.has_body?
|
193
|
-
|
193
|
+
|
194
194
|
job = middleware_stack.around_deserialization(serializer, message.receipt_handle, message.body) do
|
195
195
|
serializer.unserialize(message.body)
|
196
196
|
end
|
197
197
|
return unless job
|
198
|
-
|
198
|
+
|
199
199
|
submitter = submitter_class.new(box, serializer)
|
200
200
|
context = execution_context_class.new(submitter, {'logger' => logger})
|
201
|
-
|
201
|
+
|
202
202
|
t = Time.now
|
203
203
|
middleware_stack.around_execution(job, context) do
|
204
204
|
job.method(:run).arity.zero? ? job.run : job.run(context)
|
205
205
|
end
|
206
206
|
box.delete_message(message.receipt_handle)
|
207
|
-
|
207
|
+
|
208
208
|
delta = Time.now - t
|
209
209
|
logger.info { "[worker] Finished %s in %0.2fs" % [job.inspect, delta] }
|
210
210
|
ensure
|
211
211
|
n_flushed = box.flush!
|
212
212
|
logger.debug { "[worker] Flushed %d connection commands" % n_flushed } if n_flushed.nonzero?
|
213
213
|
end
|
214
|
-
|
214
|
+
|
215
215
|
def take_and_execute
|
216
216
|
message = @execution_queue.pop(nonblock=true)
|
217
217
|
handle_message(message)
|
@@ -222,31 +222,31 @@ class Sqewer::Worker
|
|
222
222
|
@logger.error { '[worker] Failed "%s..." with %s: %s' % [message.inspect[0..32], e.class, e.message] }
|
223
223
|
e.backtrace.each { |s| @logger.error{"\t#{s}"} }
|
224
224
|
end
|
225
|
-
|
225
|
+
|
226
226
|
def perform(message)
|
227
227
|
# Create a messagebox that buffers all the calls to Connection, so that
|
228
228
|
# we can send out those commands in one go (without interfering with senders
|
229
229
|
# on other threads, as it seems the Aws::SQS::Client is not entirely
|
230
230
|
# thread-safe - or at least not it's HTTP client part).
|
231
231
|
box = Sqewer::ConnectionMessagebox.new(connection)
|
232
|
-
|
232
|
+
|
233
233
|
job = middleware_stack.around_deserialization(serializer, message.receipt_handle, message.body) do
|
234
234
|
serializer.unserialize(message.body)
|
235
235
|
end
|
236
236
|
return unless job
|
237
|
-
|
237
|
+
|
238
238
|
submitter = submitter_class.new(box, serializer)
|
239
239
|
context = execution_context_class.new(submitter, {'logger' => logger})
|
240
|
-
|
240
|
+
|
241
241
|
t = Time.now
|
242
242
|
middleware_stack.around_execution(job, context) do
|
243
243
|
job.method(:run).arity.zero? ? job.run : job.run(context)
|
244
244
|
end
|
245
|
-
|
245
|
+
|
246
246
|
# Perform two flushes, one for any possible jobs the job has spawned,
|
247
247
|
# and one for the job delete afterwards
|
248
248
|
box.delete_message(message.receipt_handle)
|
249
|
-
|
249
|
+
|
250
250
|
delta = Time.now - t
|
251
251
|
logger.info { "[worker] Finished %s in %0.2fs" % [job.inspect, delta] }
|
252
252
|
ensure
|