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 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