sqewer 4.2.0 → 5.0.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/DETAILS.md +19 -6
- data/lib/sqewer/version.rb +1 -1
- data/lib/sqewer/worker.rb +58 -9
- data/spec/sqewer/worker_spec.rb +1 -34
- data/sqewer.gemspec +3 -4
- metadata +2 -3
- data/lib/sqewer/isolator.rb +0 -72
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3fe9d14faeab2b77f5551ae617cf50b24b803698
|
4
|
+
data.tar.gz: 10973f30ea8433ebc3f0cdc2a4b8ae8705a24476
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc139a5322ae65835d5ebdd0d95568e36f5d54a029340a6b834cd8528b64168c58d80e9b62790707b7eb69bdd9616cb20e781119f4d0e6cc713c82c226749bfe
|
7
|
+
data.tar.gz: 52139badc74df3a2b208415063c5f336b98c294b630abbf95d20165882a01a6908d69139d73ba5e73ecbc52d0b595d1c73de8ded5b2b6ea433f0da87d0ea78d9
|
data/DETAILS.md
CHANGED
@@ -171,13 +171,26 @@ started with your custom Worker of choice:
|
|
171
171
|
|
172
172
|
## Threads versus processes
|
173
173
|
|
174
|
-
|
175
|
-
|
176
|
-
|
174
|
+
sqewer uses threads. If you need to run your job from a forked subprocess (primarily for memory
|
175
|
+
management reasons) you can do so from the `run` method. Note that you might need to apply extra gymnastics
|
176
|
+
to submit extra jobs in this case, as it is the job of the controlling worker thread to submit the messages
|
177
|
+
you generate. For example, you could use a pipe. But in a more general case something like this can be used:
|
177
178
|
|
178
|
-
|
179
|
-
|
180
|
-
|
179
|
+
class MyJob
|
180
|
+
def run
|
181
|
+
pid = fork do
|
182
|
+
SomeRemoteService.reconnect # you are in the child process now
|
183
|
+
ActiveRAMGobbler.fetch_stupendously_many_things.each do |...|
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
_, status = Process.wait2(pid)
|
188
|
+
|
189
|
+
# Raise an error in the parent process to signal Sqewer that the job failed
|
190
|
+
# if the child exited with a non-0 status
|
191
|
+
raise "Child process crashed" unless status.exitstatus && status.exitstatus.zero?
|
192
|
+
end
|
193
|
+
end
|
181
194
|
|
182
195
|
## Execution and serialization wrappers (middleware)
|
183
196
|
|
data/lib/sqewer/version.rb
CHANGED
data/lib/sqewer/worker.rb
CHANGED
@@ -27,9 +27,6 @@ class Sqewer::Worker
|
|
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
|
-
# @return [#perform] The isolator to use when executing each job
|
31
|
-
attr_reader :isolator
|
32
|
-
|
33
30
|
# @return [Array<Thread>] all the currently running threads of the Worker
|
34
31
|
attr_reader :threads
|
35
32
|
|
@@ -53,7 +50,6 @@ class Sqewer::Worker
|
|
53
50
|
# @param submitter_class[Class] the class used for submitting jobs (will be instantiated by the worker for each job execution)
|
54
51
|
# @param middleware_stack[Sqewer::MiddlewareStack] the middleware stack that is going to be used
|
55
52
|
# @param logger[Logger] the logger to log execution to and to pass to the jobs
|
56
|
-
# @param isolator[Sqewer::Isolator] the isolator to encapsulate job instantiation and execution, if desired
|
57
53
|
# @param num_threads[Fixnum] how many worker threads to spawn
|
58
54
|
def initialize(connection: Sqewer::Connection.default,
|
59
55
|
serializer: Sqewer::Serializer.default,
|
@@ -61,7 +57,6 @@ class Sqewer::Worker
|
|
61
57
|
submitter_class: Sqewer::Submitter,
|
62
58
|
middleware_stack: Sqewer::MiddlewareStack.default,
|
63
59
|
logger: Logger.new($stderr),
|
64
|
-
isolator: Sqewer::Isolator.default,
|
65
60
|
num_threads: DEFAULT_NUM_THREADS)
|
66
61
|
|
67
62
|
@logger = logger
|
@@ -70,7 +65,6 @@ class Sqewer::Worker
|
|
70
65
|
@middleware_stack = middleware_stack
|
71
66
|
@execution_context_class = execution_context_class
|
72
67
|
@submitter_class = submitter_class
|
73
|
-
@isolator = isolator
|
74
68
|
@num_threads = num_threads
|
75
69
|
|
76
70
|
@threads = []
|
@@ -189,9 +183,33 @@ class Sqewer::Worker
|
|
189
183
|
|
190
184
|
def handle_message(message)
|
191
185
|
return unless message.receipt_handle
|
192
|
-
|
193
|
-
|
194
|
-
#
|
186
|
+
|
187
|
+
# Create a messagebox that buffers all the calls to Connection, so that
|
188
|
+
# we can send out those commands in one go (without interfering with senders
|
189
|
+
# on other threads, as it seems the Aws::SQS::Client is not entirely
|
190
|
+
# thread-safe - or at least not it's HTTP client part).
|
191
|
+
box = Sqewer::ConnectionMessagebox.new(connection)
|
192
|
+
return box.delete_message(message.receipt_handle) unless message.has_body?
|
193
|
+
|
194
|
+
job = middleware_stack.around_deserialization(serializer, message.receipt_handle, message.body) do
|
195
|
+
serializer.unserialize(message.body)
|
196
|
+
end
|
197
|
+
return unless job
|
198
|
+
|
199
|
+
submitter = submitter_class.new(box, serializer)
|
200
|
+
context = execution_context_class.new(submitter, {'logger' => logger})
|
201
|
+
|
202
|
+
t = Time.now
|
203
|
+
middleware_stack.around_execution(job, context) do
|
204
|
+
job.method(:run).arity.zero? ? job.run : job.run(context)
|
205
|
+
end
|
206
|
+
box.delete_message(message.receipt_handle)
|
207
|
+
|
208
|
+
delta = Time.now - t
|
209
|
+
logger.info { "[worker] Finished %s in %0.2fs" % [job.inspect, delta] }
|
210
|
+
ensure
|
211
|
+
n_flushed = box.flush!
|
212
|
+
logger.debug { "[worker] Flushed %d connection commands" % n_flushed } if n_flushed.nonzero?
|
195
213
|
end
|
196
214
|
|
197
215
|
def take_and_execute
|
@@ -204,4 +222,35 @@ class Sqewer::Worker
|
|
204
222
|
@logger.error { '[worker] Failed "%s..." with %s: %s' % [message.inspect[0..32], e.class, e.message] }
|
205
223
|
e.backtrace.each { |s| @logger.error{"\t#{s}"} }
|
206
224
|
end
|
225
|
+
|
226
|
+
def perform(message)
|
227
|
+
# Create a messagebox that buffers all the calls to Connection, so that
|
228
|
+
# we can send out those commands in one go (without interfering with senders
|
229
|
+
# on other threads, as it seems the Aws::SQS::Client is not entirely
|
230
|
+
# thread-safe - or at least not it's HTTP client part).
|
231
|
+
box = Sqewer::ConnectionMessagebox.new(connection)
|
232
|
+
|
233
|
+
job = middleware_stack.around_deserialization(serializer, message.receipt_handle, message.body) do
|
234
|
+
serializer.unserialize(message.body)
|
235
|
+
end
|
236
|
+
return unless job
|
237
|
+
|
238
|
+
submitter = submitter_class.new(box, serializer)
|
239
|
+
context = execution_context_class.new(submitter, {'logger' => logger})
|
240
|
+
|
241
|
+
t = Time.now
|
242
|
+
middleware_stack.around_execution(job, context) do
|
243
|
+
job.method(:run).arity.zero? ? job.run : job.run(context)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Perform two flushes, one for any possible jobs the job has spawned,
|
247
|
+
# and one for the job delete afterwards
|
248
|
+
box.delete_message(message.receipt_handle)
|
249
|
+
|
250
|
+
delta = Time.now - t
|
251
|
+
logger.info { "[worker] Finished %s in %0.2fs" % [job.inspect, delta] }
|
252
|
+
ensure
|
253
|
+
n_flushed = box.flush!
|
254
|
+
logger.debug { "[worker] Flushed %d connection commands" % n_flushed } if n_flushed.nonzero?
|
255
|
+
end
|
207
256
|
end
|
data/spec/sqewer/worker_spec.rb
CHANGED
@@ -8,7 +8,7 @@ describe Sqewer::Worker, :sqs => true do
|
|
8
8
|
|
9
9
|
it 'has all the necessary attributes' do
|
10
10
|
attrs = [:logger, :connection, :serializer, :middleware_stack,
|
11
|
-
:execution_context_class, :submitter_class, :
|
11
|
+
:execution_context_class, :submitter_class, :num_threads]
|
12
12
|
default_worker = described_class.default
|
13
13
|
attrs.each do | attr_name |
|
14
14
|
expect(default_worker).to respond_to(attr_name)
|
@@ -106,38 +106,5 @@ describe Sqewer::Worker, :sqs => true do
|
|
106
106
|
worker.stop
|
107
107
|
end
|
108
108
|
end
|
109
|
-
|
110
|
-
it 'sets up the processing pipeline so that jobs can execute in sequence (with processes)' do
|
111
|
-
class SecondaryJob
|
112
|
-
def run
|
113
|
-
File.open('secondary-job-run','w') {}
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
class InitialJob
|
118
|
-
def run(executor)
|
119
|
-
File.open('initial-job-run','w') {}
|
120
|
-
executor.submit!(SecondaryJob.new)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
payload = JSON.dump({job_class: 'InitialJob'})
|
125
|
-
client = Aws::SQS::Client.new
|
126
|
-
client.send_message(queue_url: ENV.fetch('SQS_QUEUE_URL'), message_body: payload)
|
127
|
-
|
128
|
-
worker = described_class.new(logger: test_logger, num_threads: 8, isolator: Sqewer::Isolator.process)
|
129
|
-
|
130
|
-
worker.start
|
131
|
-
|
132
|
-
begin
|
133
|
-
wait_for { File.exist?('initial-job-run') }.to eq(true)
|
134
|
-
wait_for { File.exist?('secondary-job-run') }.to eq(true)
|
135
|
-
|
136
|
-
File.unlink('initial-job-run')
|
137
|
-
File.unlink('secondary-job-run')
|
138
|
-
ensure
|
139
|
-
worker.stop
|
140
|
-
end
|
141
|
-
end
|
142
109
|
end
|
143
110
|
end
|
data/sqewer.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: sqewer
|
5
|
+
# stub: sqewer 5.0.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "sqewer"
|
9
|
-
s.version = "
|
9
|
+
s.version = "5.0.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Julik Tarkhanov"]
|
14
|
-
s.date = "2016-02-
|
14
|
+
s.date = "2016-02-13"
|
15
15
|
s.description = "Process jobs from SQS"
|
16
16
|
s.email = "me@julik.nl"
|
17
17
|
s.extra_rdoc_files = [
|
@@ -35,7 +35,6 @@ Gem::Specification.new do |s|
|
|
35
35
|
"lib/sqewer/contrib/appsignal_wrapper.rb",
|
36
36
|
"lib/sqewer/contrib/performable.rb",
|
37
37
|
"lib/sqewer/execution_context.rb",
|
38
|
-
"lib/sqewer/isolator.rb",
|
39
38
|
"lib/sqewer/middleware_stack.rb",
|
40
39
|
"lib/sqewer/null_logger.rb",
|
41
40
|
"lib/sqewer/serializer.rb",
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sqewer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julik Tarkhanov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk
|
@@ -216,7 +216,6 @@ files:
|
|
216
216
|
- lib/sqewer/contrib/appsignal_wrapper.rb
|
217
217
|
- lib/sqewer/contrib/performable.rb
|
218
218
|
- lib/sqewer/execution_context.rb
|
219
|
-
- lib/sqewer/isolator.rb
|
220
219
|
- lib/sqewer/middleware_stack.rb
|
221
220
|
- lib/sqewer/null_logger.rb
|
222
221
|
- lib/sqewer/serializer.rb
|
data/lib/sqewer/isolator.rb
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
# Used to isolate the execution environment of the jobs. You can use it to run each
|
2
|
-
# job in a separate process (a-la Resque) or stick to the default of running those jobs
|
3
|
-
# in threads (a-la Sidekiq).
|
4
|
-
class Sqewer::Isolator
|
5
|
-
# Used for running each job in a separate process.
|
6
|
-
class PerProcess < self
|
7
|
-
# The method called to isolate a particular job flow (both instantiation and execution)
|
8
|
-
#
|
9
|
-
# @see {Isolator#perform}
|
10
|
-
def perform(*)
|
11
|
-
require 'exceptional_fork' unless defined?(ExceptionalFork)
|
12
|
-
ExceptionalFork.fork_and_wait { super }
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
# Returns the Isolator that runs each job unserialization and execution
|
17
|
-
# as a separate process, and then ensures that that process quits cleanly.
|
18
|
-
#
|
19
|
-
# @return [Sqewer::Isolator::PerProcess] the isolator
|
20
|
-
def self.process
|
21
|
-
@per_process ||= PerProcess.new
|
22
|
-
end
|
23
|
-
|
24
|
-
# Returns the default Isolator that just wraps the instantiation/execution block
|
25
|
-
#
|
26
|
-
# @return [Sqewer::Isolator] the isolator
|
27
|
-
def self.default
|
28
|
-
@default ||= new
|
29
|
-
end
|
30
|
-
|
31
|
-
# The method called to isolate a particular job flow (both instantiation and execution)
|
32
|
-
#
|
33
|
-
# @param worker[Sqewer::Worker] the worker that is running the jobs
|
34
|
-
# @param message[Sqewer::Connection::Message] the message that is being processed
|
35
|
-
def perform(worker, message)
|
36
|
-
submitter_class = worker.submitter_class
|
37
|
-
execution_context_class = worker.execution_context_class
|
38
|
-
middleware_stack = worker.middleware_stack
|
39
|
-
connection = worker.connection
|
40
|
-
logger = worker.logger
|
41
|
-
serializer = worker.serializer
|
42
|
-
|
43
|
-
# Create a messagebox that buffers all the calls to Connection, so that
|
44
|
-
# we can send out those commands in one go (without interfering with senders
|
45
|
-
# on other threads, as it seems the Aws::SQS::Client is not entirely
|
46
|
-
# thread-safe - or at least not it's HTTP client part).
|
47
|
-
box = Sqewer::ConnectionMessagebox.new(connection)
|
48
|
-
|
49
|
-
job = middleware_stack.around_deserialization(serializer, message.receipt_handle, message.body) do
|
50
|
-
serializer.unserialize(message.body)
|
51
|
-
end
|
52
|
-
return unless job
|
53
|
-
|
54
|
-
submitter = submitter_class.new(box, serializer)
|
55
|
-
context = execution_context_class.new(submitter, {'logger' => logger})
|
56
|
-
|
57
|
-
t = Time.now
|
58
|
-
middleware_stack.around_execution(job, context) do
|
59
|
-
job.method(:run).arity.zero? ? job.run : job.run(context)
|
60
|
-
end
|
61
|
-
|
62
|
-
# Perform two flushes, one for any possible jobs the job has spawned,
|
63
|
-
# and one for the job delete afterwards
|
64
|
-
box.delete_message(message.receipt_handle)
|
65
|
-
|
66
|
-
delta = Time.now - t
|
67
|
-
logger.info { "[worker] Finished %s in %0.2fs" % [job.inspect, delta] }
|
68
|
-
ensure
|
69
|
-
n_flushed = box.flush!
|
70
|
-
logger.debug { "[worker] Flushed %d connection commands" % n_flushed } if n_flushed.nonzero?
|
71
|
-
end
|
72
|
-
end
|