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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d580e81c48aeb4f31bf0069c603c3cee0413c51d
4
- data.tar.gz: 87d546de3454ff297750d70d355a71c45340cc0a
3
+ metadata.gz: 3fe9d14faeab2b77f5551ae617cf50b24b803698
4
+ data.tar.gz: 10973f30ea8433ebc3f0cdc2a4b8ae8705a24476
5
5
  SHA512:
6
- metadata.gz: 4be05bef37befcb1e3f49021be38af632bbb4af52bfac17a4bc66fa9f81bca65406416538a59e6c94faac584f6d60884e43a46547cc5c96a9e900cd8eef7532e
7
- data.tar.gz: 9bcf12c6ff462a7ca49659077193c1b95568872c4dd38f8444899000434cf8ce83d79e555bfd7e6226c4b18f9b2fefb72cc3feec28687b65c3047fe0b7858965
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
- You can elect to use process isolation per job. Each job will then run in a forked process, on which
175
- the main worker process will `wait()`. To do so, pass `Sqewer::Isolator.process` as the `isolator:`
176
- option to the Worker
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
- proc_worker = Sqewer::Worker.new(isolator: Sqewer::Isolator.process)
179
-
180
- By default the system is working with threads only, as processes obviously have some overhead.
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
 
@@ -1,3 +1,3 @@
1
1
  module Sqewer
2
- VERSION = '4.2.0'
2
+ VERSION = '5.0.0'
3
3
  end
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
- return @connection.delete_message(message.receipt_handle) unless message.has_body?
193
- @isolator.perform(self, message)
194
- # The message delete happens within the Isolator
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
@@ -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, :isolator, :num_threads]
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 4.2.0 ruby lib
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 = "4.2.0"
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-12"
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.2.0
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-12 00:00:00.000000000 Z
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
@@ -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