sqewer 4.2.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|