hermes_messenger_of_the_gods 2.4.2 → 3.0.0.rc2

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
  SHA256:
3
- metadata.gz: b3ad7cd6b63d696cd8e765e69def47efa4496c43e1573b9dc80a966f770df0da
4
- data.tar.gz: 627776d0de4ac041fc22b6cb35c3fe2f8ee2d873375599c6136b54d00bc46d29
3
+ metadata.gz: 031b674be25f17c3c598825717fd068d52dba3130baaf7fee5780917dcf6fe18
4
+ data.tar.gz: 64909cbd8561ce4b82e7cfc3d3ddc38bc9e283b85ff99ce79dc46babd055c9eb
5
5
  SHA512:
6
- metadata.gz: 56035ce71cfc68f0e593a6ffc81236ef8512ab186f459f1b6d29f6322e161862951ba4fe82210cdee8ab189023ef4ef7fd31452f6f4df822be1d39578cfc79e9
7
- data.tar.gz: 74a55d8d43193c40f7e0e345a8dfe81fc72c3674202ed29c997259f30acf8ebda468e6b920fe88e8dbc28b5018eabf2e208cf23ae533c5f592442e60c69941af
6
+ metadata.gz: 3f829456b20fb7ed1cef756c6f8ac38fb9e214c4fa7e39bf562d9fbbda8051b660b109211e10da747590ef70e56fa2160525e59b01c2d090c01cce8d837246e1
7
+ data.tar.gz: 471bafec3eed05e291dee33d3ed73ed287a289519747264839e881ec439c777a42c498b1ae4da3d53554846bdfd06f1a298975edab96196a16ff9090786f0132
@@ -7,10 +7,8 @@ jobs:
7
7
  strategy:
8
8
  fail-fast: false
9
9
  matrix:
10
- rubyimage: ["getterminus/ruby-docker-images:3.0-je-20210920", "getterminus/ruby-docker-images:2.7-je-20210920",]
10
+ rubyVersion: ["2.7.8", "3.0.4", "3.1.2", "3.2.2"]
11
11
  runs-on: ubuntu-latest
12
- container:
13
- image: ${{ matrix.rubyimage }}
14
12
  steps:
15
13
  - name: Install SSH Key
16
14
  uses: webfactory/ssh-agent@v0.5.2
@@ -21,7 +19,11 @@ jobs:
21
19
  uses: actions/checkout@v2
22
20
  with:
23
21
  path: ${{ github.workspace }}/src/github.com/${{ github.repository }}
24
-
22
+ - uses: ruby/setup-ruby@v1
23
+ with:
24
+ ruby-version: ${{ matrix.rubyVersion }}
25
+ bundler-cache: true
26
+ cache-version: "${{ matrix.rubyVersion }}-1"
25
27
  - name: Run Test
26
28
  working-directory: ${{ github.workspace }}/src/github.com/${{ github.repository }}
27
29
  run: >
data/README.md CHANGED
@@ -105,51 +105,13 @@ class Foo
105
105
 
106
106
  attr_accessor :name
107
107
  validates :name, presence: true
108
- self.endpoints = {
109
- default: sns_endpoint("sns:arn", jitter: false),
110
- other: sns_endpoint('...')
111
- }
108
+ self.endpoints = sns_endpoint("sns:arn", jitter: false)
112
109
  end
113
110
 
114
111
  Foo.new(name: 'omg').dispatch!
115
112
  # => true
116
113
  ```
117
114
 
118
- Dispatches to each endpoint is done in a thread to allow for better performance.
119
-
120
- ##### Targeting an endpoint
121
- You may target only a specific endpoint by passing the `endpoints: ['']` option
122
- during message creation. Using the message above:
123
- `Foo.new(name: 'omg', endpoints: [:other]).dispatch!` would dispatch only to
124
- the other endpoint.
125
-
126
-
127
- #### Async message dispatch
128
- All messages contain a `dispatch_async(!)` option in addition to typical deferral.
129
- Using an async dispatch runs the dispatch in a thread. Since dispatches tend to
130
- be I/O heavy this is an ideal option if you do not care about capturing the
131
- return information or error status. You can check to determine how many messages
132
- are in-flight using the helper method
133
- `HermesMessengerOfTheGods.async_dispatches_in_progress` in the testing helpers
134
- a method named `wait_for_async_dispatches` is avaialbe which will wait for up
135
- to 5 seconds for all messages to be "delivered".
136
-
137
- ### Endpoint responses
138
- When an endpoint reports successful transmission, the response is recorded in
139
- the `message.successes` hash. This hash is keyed to correspond to the
140
- configuration `endpoints` hash.
141
-
142
- ### Endpoint Failures
143
- When an endpoint fails (raises an error) the final error raised will be stored
144
- in the `message.dispatch_errors` hash. This hash is keyed to correspond to the
145
- `endponts` configuration hash
146
-
147
- Failure Exceptions:
148
- * When all endpoints fail a `HermesMessengerOfTheGods::MessageDispatchTotalFailure` is raised
149
- * When only some endpoints fail a `HermesMessengerOfTheGods::MessageDispatchPartialFailure` is raised
150
-
151
- Both of these exepctions inherit from `HermesMessengerOfTheGods::MessageDispatchFailed`
152
-
153
115
  ## The Worker
154
116
  The worker provides a generic class to provide access for reading from
155
117
  endpoints. Only some endpoints actually provide the ability to read messages.
@@ -218,9 +180,7 @@ class MessageType
218
180
  end
219
181
 
220
182
  class AwesomeWorker < HermesMessengerOfTheGods::Worker
221
- self.endpoint = {
222
- default: sns_endpoint("sns::arn", jitter: false)
223
- }
183
+ self.endpoint = sns_endpoint("sns::arn", jitter: false)
224
184
  self.deserialize_with = MessageType
225
185
  self.deserialize_method = :omg_dude
226
186
  end
@@ -247,7 +207,7 @@ and the message into a single class.
247
207
  class MonoMessageWorker
248
208
  include HermesMessengerOftheGods::Concerns::MonoMessage
249
209
 
250
- self.endpoints = { default: sqs_endpoint("arn") } # Only 1 endpoint, options are supported
210
+ self.endpoint = sqs_endpoint("arn")
251
211
 
252
212
  def perform
253
213
  # Do magic Here
@@ -260,8 +220,8 @@ You may then start the message execution with:
260
220
  `./bin/fly_hermes start --pool=MonoMessageWorker--1`
261
221
 
262
222
 
263
- ## Protocol Buffers
264
- Hermes supports using ProtoBuffers over the wire using the
223
+ ## rotocol Buffers
224
+ Hermes supports using rotoBuffers over the wire using the
265
225
  `HermesMessengerOfTheGods::Concerns::GrpcProtobuf` mixin. Currently only JSON
266
226
  encoding is allowed.
267
227
 
@@ -371,36 +331,6 @@ array.
371
331
  * `:jsonify` - converts the trasnmitted data to json prior to transmission
372
332
  * `:from_sns` - reads and converts from JSON the serialized Message key
373
333
 
374
- ## Instrumentation
375
- HMOTG Uses `ActiveSupport::Notifications` to provide internal instrumentation
376
-
377
- All messages emited include the emitter in their payload if possible:
378
- * Messages prefixed with `hermes_messenger_of_the_gods.worker` include the
379
- `worker` key.
380
- * Messages prefixed with `hermes_messenger_of_the_gods.message` include the
381
- `message` key.
382
- * Messages prefixed with `hermes_messenger_of_the_gods.endpoint` include the
383
- `endpoint` key.
384
-
385
- **NOTE:** All messages are prefixed with `hermes_messenger_of_the_gods` but it
386
- was omitted here for brevity.
387
-
388
- | Message | Is Timed | Description | Payload Objects |
389
- | ------ | -------- | ----------- | --------------- |
390
- | `worker.starting` | | Called when the worker starts up | |
391
- | `worker.starting_job` | | Called before work begins on a job | `job` - the deserialize job being run |
392
- | `worker.run_job` | ✓ | Measured the execution of the message | `job` - the deserialize job being run |
393
- | `worker.failure` | | Invoked when a message raises an error during execution | `job` - the deserialize job being run <br> `error` - the exception raised |
394
- | `worker.fatal_error` | | Raised when an unhandled exception causes the worker to die | `exception` - The exceptoin that caused the worker to die |
395
- | `worker.deserialization` | ✓ | Called during message deserialization off the endpoint | `job` - the raw job received from the endpoint |
396
- | `message.dispatch` | ✓ | Called when a message complets the dispatch process | |
397
- | `message.dispatch_failure` | | Called once for every failed enpoint | `exception` - the error raised <br> `endpoint_name` - the name of the failed endpoint |
398
- | `endpoint.dispatch` | ✓ | Called around the actual dispatch for each endpoint | |
399
- | `endpoint.dispatch_failure` | | Called each time the endpoint fails to dispatch | `try` - The number of times the endpoint has tried to dispatch so far. First call is 1. <br> `exception` - The error that was raised causing the failure |
400
- | `endpoint.final_failure` | | Called on the last exception to before the endpoint gives up | `try` - The number of times the endpoint has tried to dispatch so far. At this point `try` equals the `:retries` config . <br> `exception` - The error that was raised causing the failure |
401
- | `endpoint.read_failure` | | An inbound message could not be handled (Malformed JSON for example) | `exception` - The unhandled error |
402
-
403
-
404
334
  ## Global Configuration
405
335
  When configuring HMOTG you can set global configuration opions like so:
406
336
 
@@ -433,24 +363,17 @@ parameter, or a block which is expected to return the message.
433
363
  Additionally the `say` method is provided which takes the desired level contsant
434
364
  as the first parameter. `say(Logger::ERROR, "foo")`
435
365
 
436
- ### Outputters
437
- Hermes ships and attaches a Basic logger for by default. The primary warning is
438
- that the basic output will default to the `inspect` output of your message. If
439
- your message contains a significant number of, or complex, instance variables
440
- There may be more info logged then is necessary to identify the message.
441
-
442
- As such you can overwrite the `inspect` method, or create the custom
443
- `to_log_s` method which will be used instead.
444
-
445
- Example regular output:
446
- ```
447
- I, [date stamp] INFO -- Worker WorkerClass::0(pid: 8): Starting Job <MessageClass: {:instance_var_1 => 123, :instance_var_2=5678}>
448
- I, [date stamp] INFO -- Worker WorkerClass::0(pid: 8): Finished Job <MessageClass: {:instance_var_1 => 123, :instance_var_2=5678}>
449
- ```
450
-
451
- Don't want any output such as during a test suite, or want to write your own?
452
- You can turn off the Basic outputter via
453
- `HermesMessengerOfTheGods::Output::Basic.unsubscribe!`
366
+ ## Upgrading
367
+ ### V2 -> V3
368
+ Upgrading to V2 to V3 includes the following breaking changes:
369
+
370
+ 1) The worker / message `endpoints` setting has been changed to just `endpoint` and takes a single endpoint definition.
371
+ 2) Outputters are no longer used. You need to log anything you want or need.
372
+ 3) There is no longer instrumentation outputted using ActiveSupport::Notifications
373
+ 4) Async Dispatches are now removed (Test helper `wait_for_async_dispatches` also removed)
374
+ 5) Partial and Total failure errors are no longer used, the superclass MessageDispatchFailed is used.
375
+ 6) Dispatch Errors / Successes are no longer included
376
+ 7) Callback system has been removed
454
377
 
455
378
  ## Development
456
379
 
@@ -4,26 +4,19 @@ require 'active_model'
4
4
  require 'active_support'
5
5
  require 'active_support/core_ext/hash'
6
6
 
7
- require 'hermes_messenger_of_the_gods/concerns/base'
8
-
9
7
  module HermesMessengerOfTheGods
10
8
  module Concerns
11
9
  module Message
12
10
  extend ActiveSupport::Concern
13
11
 
14
12
  include ActiveModel::Model
15
- include HermesMessengerOfTheGods::Concerns::Base
16
-
17
- attr_accessor :targeted_endpoints, :original_message, :source_endpoint
13
+ include EndpointBuilder::Helpers
14
+ include LoggingHelpers
18
15
 
19
- def initialize(options = {})
20
- @monitor = Monitor.new
21
- self.targeted_endpoints = options.delete(:endpoints)
22
- super
23
- end
16
+ attr_accessor :original_message, :source_endpoint
24
17
 
25
18
  def validate!
26
- raise HermesMessengerOfTheGods::ValidationError, self unless valid?
19
+ raise HermesMessengerOfTheGods::ValidationError.new(self) unless valid?
27
20
  end
28
21
 
29
22
  def dispatch
@@ -32,24 +25,6 @@ module HermesMessengerOfTheGods
32
25
  false
33
26
  end
34
27
 
35
- def dispatch_async
36
- HermesMessengerOfTheGods.increment_async_dispatches_in_progress
37
- Thread.new do
38
- dispatch
39
- ensure
40
- HermesMessengerOfTheGods.decrement_async_dispatches_in_progress
41
- end
42
- end
43
-
44
- def dispatch_async!
45
- HermesMessengerOfTheGods.increment_async_dispatches_in_progress
46
- Thread.new do
47
- dispatch!
48
- ensure
49
- HermesMessengerOfTheGods.decrement_async_dispatches_in_progress
50
- end
51
- end
52
-
53
28
  def retry_at(future_time_or_seconds_in_future)
54
29
  raise 'unable to set visiblity' if source_endpoint.nil? || original_message.nil?
55
30
  raise "endpoint type doesn't support setting execution time" unless source_endpoint.respond_to?(:set_reexecution_time)
@@ -58,60 +33,20 @@ module HermesMessengerOfTheGods
58
33
  end
59
34
 
60
35
  def dispatch!(endpoint_args: {})
61
- run_callbacks :dispatch do
62
- validate!
63
-
64
- endpoints.collect do |ep_name, endpoint|
65
- next if targeted_endpoints && !targeted_endpoints.include?(ep_name)
66
-
67
- begin
68
- endpoint.dispatch!(self, endpoint_args) unless HermesMessengerOfTheGods.config.stub_dispatch
69
- register_success(ep_name, endpoint.result)
70
- rescue StandardError => e
71
- say_error(e)
72
- register_failure(ep_name, e)
73
- ensure
74
- endpoint.teardown
75
- end
76
- end
36
+ validate!
77
37
 
78
- unless dispatch_errors.empty?
79
- klass = if successes.empty?
80
- HermesMessengerOfTheGods::MessageDispatchTotalFailure
81
- else
82
- HermesMessengerOfTheGods::MessageDispatchPartialFailure
83
- end
84
-
85
- ex = klass.new
86
- ex.exceptions = dispatch_errors
87
- raise ex
88
- end
38
+ begin
39
+ endpoint.dispatch!(self, endpoint_args) unless HermesMessengerOfTheGods.config.stub_dispatch
40
+ rescue StandardError => e
41
+ say_error(e)
42
+ raise HermesMessengerOfTheGods::MessageDispatchFailed, e.message
43
+ ensure
44
+ endpoint.teardown
89
45
  end
90
46
 
91
47
  true
92
48
  end
93
49
 
94
- def register_failure(ep_name, error)
95
- @monitor.synchronize do
96
- instrument(:dispatch_failure, endpoint_name: ep_name, exception: error)
97
- dispatch_errors[ep_name] = error
98
- end
99
- end
100
-
101
- def register_success(ep_name, return_value)
102
- @monitor.synchronize do
103
- successes[ep_name] = return_value
104
- end
105
- end
106
-
107
- def dispatch_errors
108
- @dispatch_errors ||= {}
109
- end
110
-
111
- def successes
112
- @successes ||= {}
113
- end
114
-
115
50
  def _build_for_transmission
116
51
  to_message
117
52
  end
@@ -131,14 +66,16 @@ module HermesMessengerOfTheGods
131
66
  end
132
67
 
133
68
  included do
134
- class_attribute :_defined_attributes, :_endpoints, :_circuit_breaker_errors, :_max_consecutive_failures
135
-
136
- define_model_callbacks :dispatch
137
-
138
- around_dispatch { |_, blk| instrument(:dispatch, &blk) }
69
+ class_attribute :_defined_attributes, :_endpoint, :_circuit_breaker_errors, :_max_consecutive_failures
139
70
  end
140
71
 
141
72
  class_methods do
73
+ def send_messages(messages, options = {})
74
+ raise ArgumentError, "All messages must be #{self.class}" unless messages.all?{|m| m.is_a?(self) }
75
+ messages.map(&:validate!)
76
+ messages.first.endpoint.bulk_dispatch!(messages, options)
77
+ end
78
+
142
79
  def from_message(attrs = {}, &blk)
143
80
  attrs = attrs.slice(*_defined_attributes.map(&:to_s))
144
81
  new(attrs, &blk)
@@ -150,8 +87,8 @@ module HermesMessengerOfTheGods
150
87
  super
151
88
  end
152
89
 
153
- def endpoints
154
- _endpoints
90
+ def endpoint
91
+ _endpoint
155
92
  end
156
93
 
157
94
  def circuit_breaker_errors
@@ -185,10 +122,8 @@ module HermesMessengerOfTheGods
185
122
  self._circuit_breaker_errors = val
186
123
  end
187
124
 
188
- def endpoints=(val)
189
- raise 'Endpoints expects a hash {endpoint_name: endpoint_handler}' unless val.is_a?(Hash)
190
-
191
- self._endpoints = val
125
+ def endpoint=(val)
126
+ self._endpoint = val
192
127
  end
193
128
  end
194
129
  end
@@ -10,12 +10,14 @@ module HermesMessengerOfTheGods
10
10
  module MonoMessage
11
11
  extend ActiveSupport::Concern
12
12
 
13
- include HermesMessengerOfTheGods::Concerns::Message
13
+ def self.included(other)
14
+ other.include HermesMessengerOfTheGods::Concerns::Message
15
+ end
14
16
 
15
17
  class_methods do
16
- def endpoints=(*args)
18
+ def endpoint=(*args)
17
19
  super
18
- worker_klass.endpoints = endpoints
20
+ worker_klass.endpoint = endpoint
19
21
  end
20
22
 
21
23
  def circuit_breaker_errors=(*args)
@@ -40,7 +42,7 @@ module HermesMessengerOfTheGods
40
42
  me = self
41
43
  @worker_klass ||= Class.new do
42
44
  include HermesMessengerOfTheGods::Concerns::Worker
43
- self.endpoints = me.endpoints
45
+ self.endpoint = me.endpoint
44
46
  self.circuit_breaker_errors = me.circuit_breaker_errors
45
47
  self.max_consecutive_failures = me.max_consecutive_failures
46
48
  self.deserialize_with = me
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'active_support'
4
4
 
5
- require_relative './base'
6
5
  require_relative '../status_server'
7
6
 
8
7
  module HermesMessengerOfTheGods
@@ -10,7 +9,8 @@ module HermesMessengerOfTheGods
10
9
  module Worker
11
10
  extend ActiveSupport::Concern
12
11
 
13
- include HermesMessengerOfTheGods::Concerns::Base
12
+ include EndpointBuilder::Helpers
13
+ include LoggingHelpers
14
14
 
15
15
  included do
16
16
  attr_writer :name
@@ -37,7 +37,6 @@ module HermesMessengerOfTheGods
37
37
  trap(:TERM) { endpoint.shutdown! }
38
38
  trap(:INT) { endpoint.shutdown! }
39
39
 
40
- instrument(:starting)
41
40
  self.consecutive_failures = 0
42
41
  endpoint.work_off do |job, original_message|
43
42
  self.last_task_performed = Time.now
@@ -45,7 +44,6 @@ module HermesMessengerOfTheGods
45
44
  built_job = deserialize(job)
46
45
  built_job.original_message = original_message
47
46
  built_job.source_endpoint = endpoint
48
- instrument(:starting_job, job: built_job)
49
47
  built_job.validate! if built_job.respond_to?(:validate!)
50
48
  run_job(built_job)
51
49
  handle_success(built_job, job)
@@ -55,7 +53,6 @@ module HermesMessengerOfTheGods
55
53
  max_consecutive_failures = self.class.max_consecutive_failures
56
54
 
57
55
  if max_consecutive_failures.is_a?(Integer) && HermesMessengerOfTheGods.config.kill_on_consecutive_failures && consecutive_failures >= max_consecutive_failures
58
- instrument(:consecutive_failure_shutdown)
59
56
  return Process.kill('TERM', Process.getpgid(Process.ppid))
60
57
  end
61
58
 
@@ -72,46 +69,35 @@ module HermesMessengerOfTheGods
72
69
  throw(:skip_delete, true)
73
70
  end
74
71
  rescue StandardError => e
75
- instrument(:fatal_error, exception: e)
76
72
  raise
77
73
  ensure
78
74
  endpoint.teardown
79
75
  end
80
76
 
81
77
  def run_job(job)
82
- instrument(:run_job, job: job) do
83
- if respond_to?(:perform)
84
- perform(job)
85
- elsif job.respond_to?(:perform)
86
- job.perform
87
- else
88
- raise 'You need to define a run_job method in the worker, or a perform method on the message'
89
- end
78
+ if respond_to?(:perform)
79
+ perform(job)
80
+ elsif job.respond_to?(:perform)
81
+ job.perform
82
+ else
83
+ raise 'You need to define a run_job method in the worker, or a perform method on the message'
90
84
  end
91
85
  end
92
86
 
93
87
  def handle_success(built_job, raw_job)
94
- instrument(:success, job: built_job)
95
88
  endpoint.handle_success(raw_job)
96
89
  self.consecutive_failures = 0
97
90
  end
98
91
 
99
92
  def handle_failure(job, e)
100
- instrument(:failure, job: job, error: e)
101
93
  endpoint.handle_failure(job, e)
102
94
  self.consecutive_failures ||= 0
103
95
  self.consecutive_failures += 1
104
96
  end
105
97
 
106
98
  def deserialize(raw_job)
107
- instrument(:deserialization, job: raw_job) do
108
- deserialize_method = self.class.deserialize_method || :from_message
109
- self.class.deserialize_with.send(deserialize_method, raw_job)
110
- end
111
- end
112
-
113
- def endpoint
114
- @endpoint ||= endpoints.values.first
99
+ deserialize_method = self.class.deserialize_method || :from_message
100
+ self.class.deserialize_with.send(deserialize_method, raw_job)
115
101
  end
116
102
 
117
103
  def log_message_prefix
@@ -124,7 +110,7 @@ module HermesMessengerOfTheGods
124
110
  end
125
111
 
126
112
  class_methods do
127
- attr_reader :endpoints
113
+ attr_reader :endpoint
128
114
  attr_accessor :deserialize_with, :deserialize_method
129
115
 
130
116
  def build_worker
@@ -166,11 +152,10 @@ module HermesMessengerOfTheGods
166
152
  @circuit_breaker_errors = val
167
153
  end
168
154
 
169
- def endpoints=(val)
155
+ def endpoint=(val)
170
156
  raise 'Expected an endpoint' unless val.is_a?(Hash)
171
- raise 'Workers can only have one defined endpoint' unless val.length == 1
172
157
 
173
- @endpoints = val
158
+ @endpoint = val
174
159
  end
175
160
  end
176
161
  end
@@ -12,8 +12,8 @@ module HermesMessengerOfTheGods
12
12
  extend ActiveSupport::Concern
13
13
 
14
14
  included do
15
- def endpoints
16
- @endpoints ||= self.class.build_endpoints
15
+ def endpoint
16
+ @endpoint ||= self.class.build_endpoint
17
17
  end
18
18
  end
19
19
 
@@ -36,10 +36,8 @@ module HermesMessengerOfTheGods
36
36
  end
37
37
  end
38
38
 
39
- def build_endpoints
40
- endpoints.each.with_object({}) do |(k, v), hsh|
41
- hsh[k] = EndpointBuilder.build(k, v[:klass], *v[:args])
42
- end
39
+ def build_endpoint
40
+ EndpointBuilder.build('default', endpoint[:klass], *endpoint[:args])
43
41
  end
44
42
  end
45
43
  end
@@ -1,13 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../concerns/base'
4
-
5
3
  module HermesMessengerOfTheGods
6
4
  module Endpoints
7
5
  class Base
8
- extend ActiveModel::Callbacks
9
-
10
- include HermesMessengerOfTheGods::Concerns::Base
11
6
 
12
7
  DEFAULT_OPTIONS = { retries: 3, jitter: true, backoff: :linear, jsonify: true }.freeze
13
8
  DEFAULT_RETRYS = {
@@ -17,10 +12,6 @@ module HermesMessengerOfTheGods
17
12
 
18
13
  attr_accessor :options, :endpoint, :errors, :result, :name
19
14
 
20
- define_model_callbacks :dispatch
21
-
22
- around_dispatch { |_, blk| instrument(:dispatch, &blk) }
23
-
24
15
  def initialize(name, endpoint, options = {})
25
16
  self.name = name
26
17
  self.options = self.class::DEFAULT_OPTIONS.merge(options)
@@ -34,22 +25,30 @@ module HermesMessengerOfTheGods
34
25
  false
35
26
  end
36
27
 
28
+ def _transmit_payload(message, options)
29
+ to_transmit_payload(transform_message(message), message, options)
30
+ end
31
+
32
+ def bulk_dispatch!(messages, options)
33
+ payloads = messages.map do |message|
34
+ _transmit_payload(message, options)
35
+ end
36
+
37
+ bulk_transmit(payloads)
38
+ end
39
+
37
40
  def dispatch!(message, options = {})
38
- run_callbacks :dispatch do
39
- retry_number = 0
40
- begin
41
- self.result = transmit(transform_message(message), message, options)
42
- rescue StandardError => e
43
- errors << e
44
- retry_number += 1
45
- if retry_number < max_retries && retry_from(e)
46
- instrument(:dispatch_failure, try: retry_number, exception: e)
47
- backoff(retry_number)
48
- retry
49
- else
50
- instrument(:final_failure, try: retry_number, exception: e)
51
- raise
52
- end
41
+ retry_number = 0
42
+ begin
43
+ self.result = transmit(_transmit_payload(message, options))
44
+ rescue StandardError => e
45
+ errors << e
46
+ retry_number += 1
47
+ if retry_number < max_retries && retry_from(e)
48
+ backoff(retry_number)
49
+ retry
50
+ else
51
+ raise
53
52
  end
54
53
  end
55
54
 
@@ -6,18 +6,43 @@ require 'json'
6
6
  module HermesMessengerOfTheGods
7
7
  module Endpoints
8
8
  class Sns < Base
9
- def sns_topic
10
- @sns_topic ||= Aws::SNS::Topic.new(endpoint,
11
- {
12
- client: HermesMessengerOfTheGods.configuration.sns_client,
13
- }.merge(options[:client_options] || {}))
9
+ def sns_client
10
+ HermesMessengerOfTheGods.configuration.sns_client
14
11
  end
15
12
 
16
- def transmit(message, raw_message, dispatch_options = {})
13
+ def to_transmit_payload(message, raw_message, dispatch_options = {})
17
14
  pub_opts = fetch_option(:publish_options, raw_message) || {}
18
15
 
19
16
  message = JSON.dump(message) if options[:jsonify]
20
- sns_topic.publish(pub_opts.merge(dispatch_options, message: message))
17
+
18
+ pub_opts.merge(dispatch_options, message: message)
19
+ end
20
+
21
+ def transmit(payload)
22
+ bulk_transmit([payload])
23
+ end
24
+
25
+ def bulk_transmit(payloads)
26
+ # Batch Publish requires a message id, so we'll generate one if it's not set
27
+ all_publish_batch_request_entries = payloads.map do |payload|
28
+ {id: SecureRandom.uuid}.merge(payload)
29
+ end
30
+
31
+ failed_msgs = []
32
+
33
+ all_publish_batch_request_entries.each_slice(10) do |publish_batch_request_entries|
34
+ resp = sns_client.publish_batch(topic_arn: endpoint, publish_batch_request_entries: publish_batch_request_entries)
35
+ failed_msgs.concat(resp.failed)
36
+ end
37
+
38
+ if failed_msgs.any?
39
+ all_sender_fault = failed_msgs.all?(&:sender_fault)
40
+ raise FatalError, "Error in dispatching: #{failed_msgs[0].message}" if all_sender_fault
41
+
42
+ raise "Some messages failed to send due to recoverable error #{failed_msgs[0].message}"
43
+ end
44
+
45
+ true
21
46
  end
22
47
  end
23
48
  end
@@ -111,10 +111,13 @@ module HermesMessengerOfTheGods
111
111
 
112
112
  !skip_delete
113
113
  rescue StandardError => e
114
- instrument(:read_failure, exception: e)
115
114
  false
116
115
  end
117
116
 
117
+ def sqs_client
118
+ HermesMessengerOfTheGods.configuration.sqs_client
119
+ end
120
+
118
121
  def queue
119
122
  @queue ||= Aws::SQS::Queue.new(
120
123
  endpoint,
@@ -139,11 +142,38 @@ module HermesMessengerOfTheGods
139
142
  approximate_pending_messages.positive?
140
143
  end
141
144
 
142
- def transmit(message, raw_message, dispatch_options = {})
145
+ def to_transmit_payload(message, raw_message, dispatch_options = {})
143
146
  send_opts = fetch_option(:send_options, raw_message) || {}
144
147
 
145
148
  message = JSON.dump(message) if options[:jsonify]
146
- queue.send_message(send_opts.merge(dispatch_options, message_body: message))
149
+
150
+ send_opts.merge(dispatch_options, message_body: message)
151
+ end
152
+
153
+ def transmit(payload)
154
+ bulk_transmit([payload])
155
+ end
156
+
157
+ def bulk_transmit(payloads)
158
+ all_entries = payloads.map! do |payload|
159
+ {id: SecureRandom.uuid}.merge(payload)
160
+ end
161
+
162
+ failed_msgs = []
163
+
164
+ all_entries.each_slice(10) do |entries|
165
+ resp = sqs_client.send_message_batch(queue_url: endpoint, entries: entries)
166
+ failed_msgs.concat(resp.failed)
167
+ end
168
+
169
+ if failed_msgs.any?
170
+ all_sender_fault = failed_msgs.failed.all?(&:sender_fault)
171
+ raise FatalError, "Error in dispatching: #{failed_msgs[0].message}" if all_sender_fault
172
+
173
+ raise "Some messages failed to send due to recoverable error #{failed_msgs[0].message}"
174
+ end
175
+
176
+ true
147
177
  end
148
178
 
149
179
  private
@@ -1,27 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module HermesMessengerOfTheGods
4
- if defined?(ActiveModel::ValidationError)
5
- class ValidationError < ActiveModel::ValidationError; end
6
- else
7
- class ValidationError < StandardError
8
- attr_reader :model
3
+ require 'active_model'
9
4
 
10
- def initialize(model)
11
- @model = model
12
- errors = @model.errors.full_messages.join(', ')
13
- super(errors)
14
- end
15
- end
16
- end
5
+ module HermesMessengerOfTheGods
6
+ class ValidationError < ActiveModel::ValidationError; end
17
7
 
18
8
  class MessageDispatchFailed < StandardError
19
9
  attr_accessor :exceptions
20
10
  end
21
11
 
22
- class MessageDispatchTotalFailure < MessageDispatchFailed; end
23
- class MessageDispatchPartialFailure < MessageDispatchFailed; end
24
-
25
12
  module Endpoints
26
13
  class FatalError < RuntimeError; end
27
14
  end
@@ -4,8 +4,6 @@ module HermesMessengerOfTheGods
4
4
  module Endpoints
5
5
  class Local < Base
6
6
  class << self
7
- attr_accessor :transmit_method
8
-
9
7
  def clear_queue!
10
8
  @received = nil
11
9
  end
@@ -21,20 +19,23 @@ module HermesMessengerOfTheGods
21
19
  self.class.received[endpoint]
22
20
  end
23
21
 
24
- def transmit(msg, raw_msg, opts = {})
25
- if self.class.transmit_method
26
- self.class.transmit_method.call(self, msg, raw_msg)
27
- else
28
- do_transmit(msg, opts, raw_msg)
29
- end
22
+ def to_transmit_payload(message, raw_message, dispatch_options = {})
23
+ { message: message, raw_message: raw_message, options: dispatch_options }
24
+ end
25
+
26
+ def transmit(payload)
27
+ bulk_transmit([payload])
30
28
  end
31
29
 
32
- def do_transmit(msg, opts, raw_message = {})
33
- pub_opts = fetch_option(:publish_options, raw_message) || {}
34
- new_msg = { message: msg, options: opts.merge(pub_opts) }
35
- received << new_msg
36
- on_receive&.call(new_msg)
37
- "Recorded msg ##{self.class.received[endpoint].length} for #{endpoint}"
30
+ def bulk_transmit(payloads)
31
+ payloads.each do |msg|
32
+ pub_opts = fetch_option(:publish_options, msg[:raw_message]) || {}
33
+
34
+ new_msg = { message: msg[:message], options: msg[:options].merge(pub_opts) }
35
+ received << new_msg
36
+ on_receive&.call(new_msg)
37
+ "Recorded msg ##{self.class.received[endpoint].length} for #{endpoint}"
38
+ end
38
39
  end
39
40
  end
40
41
  end
@@ -44,7 +45,6 @@ if defined?(RSpec)
44
45
  RSpec.configure do |config|
45
46
  config.after(:each) do
46
47
  HermesMessengerOfTheGods::Endpoints::Local.clear_queue!
47
- HermesMessengerOfTheGods::Endpoints::Local.transmit_method = nil
48
48
  end
49
49
  end
50
50
  end
@@ -17,7 +17,6 @@ RSpec::Matchers.define :dispatch do |message|
17
17
  return false if @matched_early
18
18
 
19
19
  proc.call
20
- wait_for_async_dispatches
21
20
 
22
21
  !@endpoint.received.detect do |m|
23
22
  a_hash_including(message).matches?(m[:message]) && (
@@ -45,14 +45,6 @@ module HermesMessengerOfTheGods
45
45
  build_fake_endpoint(klass, endpoint, *args)
46
46
  end
47
47
  end
48
-
49
- def wait_for_async_dispatches
50
- return if HermesMessengerOfTheGods.async_dispatches_in_progress.zero?
51
-
52
- Timeout.timeout(5) do
53
- sleep 0.01 until HermesMessengerOfTheGods.async_dispatches_in_progress.zero?
54
- end
55
- end
56
48
  end
57
49
  end
58
50
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HermesMessengerOfTheGods
4
- VERSION = '2.4.2'
4
+ VERSION = '3.0.0.rc2'
5
5
  end
@@ -8,7 +8,6 @@ require 'hermes_messenger_of_the_gods/configuration'
8
8
  require 'hermes_messenger_of_the_gods/logging_helpers'
9
9
  require 'hermes_messenger_of_the_gods/endpoint_builder'
10
10
 
11
- require 'hermes_messenger_of_the_gods/concerns/base'
12
11
  require 'hermes_messenger_of_the_gods/concerns/message'
13
12
  require 'hermes_messenger_of_the_gods/concerns/worker'
14
13
  require 'hermes_messenger_of_the_gods/concerns/mono_message'
@@ -16,8 +15,6 @@ require 'hermes_messenger_of_the_gods/concerns/grpc_protobuf'
16
15
 
17
16
  require 'hermes_messenger_of_the_gods/endpoints'
18
17
 
19
- require 'hermes_messenger_of_the_gods/output/basic'
20
-
21
18
  module HermesMessengerOfTheGods
22
19
  class << self
23
20
  def configuration
@@ -29,29 +26,5 @@ module HermesMessengerOfTheGods
29
26
  configuration
30
27
  end
31
28
  alias config configure
32
-
33
- def async_dispatches_in_progress
34
- @async_dispatches_in_progress ||= 0
35
- end
36
-
37
- def increment_async_dispatches_in_progress
38
- monitor.synchronize do
39
- self.async_dispatches_in_progress += 1
40
- end
41
- end
42
-
43
- def decrement_async_dispatches_in_progress
44
- monitor.synchronize do
45
- self.async_dispatches_in_progress -= 1
46
- end
47
- end
48
-
49
- private
50
-
51
- attr_writer :async_dispatches_in_progress
52
-
53
- def monitor
54
- @monitor ||= Monitor.new
55
- end
56
29
  end
57
30
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hermes_messenger_of_the_gods
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.2
4
+ version: 3.0.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Malinconico
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-07-27 00:00:00.000000000 Z
12
+ date: 2023-10-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activemodel
@@ -217,7 +217,6 @@ files:
217
217
  - exe/fly_hermes
218
218
  - hermes_messenger_of_the_gods.gemspec
219
219
  - lib/hermes_messenger_of_the_gods.rb
220
- - lib/hermes_messenger_of_the_gods/concerns/base.rb
221
220
  - lib/hermes_messenger_of_the_gods/concerns/grpc_protobuf.rb
222
221
  - lib/hermes_messenger_of_the_gods/concerns/message.rb
223
222
  - lib/hermes_messenger_of_the_gods/concerns/mono_message.rb
@@ -230,7 +229,6 @@ files:
230
229
  - lib/hermes_messenger_of_the_gods/endpoints/sqs.rb
231
230
  - lib/hermes_messenger_of_the_gods/exceptions.rb
232
231
  - lib/hermes_messenger_of_the_gods/logging_helpers.rb
233
- - lib/hermes_messenger_of_the_gods/output/basic.rb
234
232
  - lib/hermes_messenger_of_the_gods/status_server.rb
235
233
  - lib/hermes_messenger_of_the_gods/testing/array_endpoint.rb
236
234
  - lib/hermes_messenger_of_the_gods/testing/dispatch_matcher.rb
@@ -252,9 +250,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
252
250
  version: '0'
253
251
  required_rubygems_version: !ruby/object:Gem::Requirement
254
252
  requirements:
255
- - - ">="
253
+ - - ">"
256
254
  - !ruby/object:Gem::Version
257
- version: '0'
255
+ version: 1.3.1
258
256
  requirements: []
259
257
  rubygems_version: 3.2.22
260
258
  signing_key:
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support'
4
- require 'active_support/core_ext/object/blank'
5
-
6
- module HermesMessengerOfTheGods
7
- module Concerns
8
- module Base
9
- extend ActiveSupport::Concern
10
-
11
- include EndpointBuilder::Helpers
12
- include LoggingHelpers
13
-
14
- included do
15
- def instrument(name, payload = {}, &blk)
16
- name = [
17
- 'hermes_messenger_of_the_gods',
18
- notification_prefix,
19
- name,
20
- ].reject(&:blank?).join('.')
21
-
22
- payload[_instrument_key] ||= self if _instrument_key
23
- ActiveSupport::Notifications.instrument(name, payload, &blk)
24
- end
25
-
26
- def notification_prefix
27
- _instrument_key
28
- end
29
-
30
- def _instrument_key
31
- if is_a?(HermesMessengerOfTheGods::Concerns::Worker)
32
- :worker
33
- elsif is_a?(HermesMessengerOfTheGods::Concerns::Message)
34
- :message
35
- elsif is_a?(HermesMessengerOfTheGods::Endpoints::Base)
36
- :endpoint
37
- end
38
- end
39
- end
40
- end
41
- end
42
- end
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Worker Output
4
- module HermesMessengerOfTheGods
5
- module Output
6
- module Basic
7
- @subscriptions = []
8
-
9
- def self.unsubscribe!
10
- @subscriptions.map { |s| ActiveSupport::Notifications.unsubscribe(s) }
11
- end
12
-
13
- def self.subscribe!
14
- unsubscribe!
15
-
16
- @subscriptions = [
17
- ActiveSupport::Notifications.subscribe('hermes_messenger_of_the_gods.worker.starting') do |*_, payload|
18
- payload[:worker].say_info { 'Starting Worker' }
19
- end,
20
- ActiveSupport::Notifications.subscribe('hermes_messenger_of_the_gods.worker.run_job') do |_, start, finish, _, payload|
21
- payload[:worker].say_debug { "Completed #{to_log_s(payload[:job])} in #{finish - start}s" }
22
- end,
23
- ActiveSupport::Notifications.subscribe('hermes_messenger_of_the_gods.worker.starting_job') do |*_, payload|
24
- payload[:worker].say_info { "Starting Job #{to_log_s(payload[:job])}" }
25
- end,
26
- ActiveSupport::Notifications.subscribe('hermes_messenger_of_the_gods.worker.success') do |*_, payload|
27
- payload[:worker].say_info { "Finished Job #{to_log_s(payload[:job])}" }
28
- end,
29
- ActiveSupport::Notifications.subscribe('hermes_messenger_of_the_gods.worker.failure') do |*_, payload|
30
- payload[:worker].say_error { "Error in #{to_log_s(payload[:job])}: #{payload[:error].message}" }
31
- payload[:worker].say_debug { "backtrace: #{payload[:error].backtrace}" }
32
- end,
33
- ActiveSupport::Notifications.subscribe('hermes_messenger_of_the_gods.worker.fatal_error') do |*_, payload|
34
- payload[:worker].say_error { "Fatal Error: #{to_log_s(payload[:job])}: #{payload[:exception].message}" }
35
- payload[:worker].say_debug { "backtrace: #{payload[:exception].backtrace}" }
36
- end,
37
- # Message Output
38
- ActiveSupport::Notifications.subscribe('hermes_messenger_of_the_gods.message.dispatch') do |_, _start, _finish, _, payload|
39
- payload[:message].say_debug { "Dispatch complete in #{to_log_s(payload[:job])}s" }
40
- end,
41
- ActiveSupport::Notifications.subscribe('hermes_messenger_of_the_gods.message.dispatch_failure') do |*_, payload|
42
- payload[:message].say_error { "Dispatch failure to #{to_log_s(payload[:job])}: #{payload[:exception].inspect}" }
43
- end,
44
- # Endpoint Output
45
- ActiveSupport::Notifications.subscribe('hermes_messenger_of_the_gods.endpoint.dispatch') do |_, start, finish, _, payload|
46
- payload[:endpoint].say_debug { "Dispatch complete in #{finish - start}s #{' FAILED' if payload.key?(:exception)}" }
47
- end,
48
- ActiveSupport::Notifications.subscribe(/hermes_messenger_of_the_gods.endpoint.(dispatch|final)_failure/) do |name, *_, payload|
49
- payload[:endpoint].say_error { "Dispatch #{'final ' if name.include?('final_')} failure ##{payload[:try]} to #{payload[:endpoint_name]}: #{payload[:exception].inspect}" }
50
- end,
51
- ActiveSupport::Notifications.subscribe(/hermes_messenger_of_the_gods.endpoint.read_failure/) do |_name, *_, payload|
52
- payload[:endpoint].say_error { "A message was received that could not be decoded: #{payload[:exception].message}" }
53
- end,
54
- ]
55
- end
56
- subscribe!
57
-
58
- def self.to_log_s(item)
59
- if item.respond_to?(:to_log_s)
60
- item.to_log_s
61
- else
62
- item.inspect
63
- end
64
- end
65
- end
66
- end
67
- end