artery 1.1.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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +43 -0
  4. data/Rakefile +29 -0
  5. data/app/models/concerns/artery/message_model.rb +53 -0
  6. data/config/routes.rb +4 -0
  7. data/db/migrate/20170110090949_create_artery_messages.rb +14 -0
  8. data/db/migrate/20170116143013_create_artery_last_model_updates.rb +10 -0
  9. data/db/migrate/20170420155129_change_last_model_updates_to_subscription_infos.rb +17 -0
  10. data/db/migrate/20171020104646_add_subscriber_to_artery_subscription_infos.rb +5 -0
  11. data/db/migrate/20181211110018_add_latest_index_to_artery_subscription_infos.rb +5 -0
  12. data/db/migrate/20200109120304_add_index_on_model_to_artery_messages.rb +9 -0
  13. data/db/migrate/20200109120305_remove_last_message_at_from_artery_subscription_infos.rb +9 -0
  14. data/db/migrate/20240411120304_add_synchronization_heartbeat_to_artery_subscription_infos.rb +11 -0
  15. data/exe/artery-check +9 -0
  16. data/exe/artery-clean +9 -0
  17. data/exe/artery-sync +9 -0
  18. data/exe/artery-worker +9 -0
  19. data/lib/artery/active_record/message.rb +41 -0
  20. data/lib/artery/active_record/subscription_info.rb +50 -0
  21. data/lib/artery/active_record.rb +8 -0
  22. data/lib/artery/backend.rb +95 -0
  23. data/lib/artery/backends/base.rb +41 -0
  24. data/lib/artery/backends/fake.rb +24 -0
  25. data/lib/artery/backends/nats_pure.rb +86 -0
  26. data/lib/artery/check.rb +48 -0
  27. data/lib/artery/config.rb +72 -0
  28. data/lib/artery/engine.rb +16 -0
  29. data/lib/artery/errors.rb +76 -0
  30. data/lib/artery/healthz_subscription.rb +14 -0
  31. data/lib/artery/model/callbacks.rb +40 -0
  32. data/lib/artery/model/subscriptions.rb +147 -0
  33. data/lib/artery/model.rb +96 -0
  34. data/lib/artery/no_brainer/message.rb +67 -0
  35. data/lib/artery/no_brainer/subscription_info.rb +67 -0
  36. data/lib/artery/no_brainer.rb +8 -0
  37. data/lib/artery/routing.rb +63 -0
  38. data/lib/artery/subscription/incoming_message.rb +94 -0
  39. data/lib/artery/subscription/synchronization.rb +221 -0
  40. data/lib/artery/subscription.rb +136 -0
  41. data/lib/artery/subscriptions.rb +42 -0
  42. data/lib/artery/sync.rb +35 -0
  43. data/lib/artery/version.rb +5 -0
  44. data/lib/artery/worker.rb +75 -0
  45. data/lib/artery/worker_healthz_subscription.rb +23 -0
  46. data/lib/artery.rb +56 -0
  47. data/lib/multiblock_has_block.rb +11 -0
  48. data/lib/tasks/artery_tasks.rake +6 -0
  49. metadata +160 -0
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Artery
4
+ module Routing
5
+ class URI
6
+ attr_accessor :service, :model, :action, :plural
7
+
8
+ def initialize(arg)
9
+ case arg
10
+ when URI
11
+ @service = arg.service
12
+ @model = arg.model
13
+ @action = arg.action
14
+ @plural = arg.plural
15
+ when String
16
+ @service, model, @action = arg.split('.').map(&:to_sym)
17
+ @model = model.to_s.singularize.to_sym
18
+ @plural = (@model != model)
19
+ when Hash
20
+ @service = arg[:service] || Artery.service_name
21
+ @model = arg[:model].try(:to_sym)
22
+ @action = arg[:action].try(:to_sym)
23
+ @plural = arg[:plural]
24
+ else
25
+ raise ArgumentError, 'Unknown argument format'
26
+ end
27
+ raise(ArgumentError, 'service and model must be provided') if @service.blank? || @model.blank?
28
+ end
29
+
30
+ def to_route
31
+ [@service, route_model, @action].join('.')
32
+ end
33
+ alias to_s to_route
34
+
35
+ def plural?
36
+ @plural
37
+ end
38
+
39
+ def route_model
40
+ (plural? ? model.to_s.pluralize : model)
41
+ end
42
+
43
+ # Make them identical for Hash if route is identical
44
+ def ==(other)
45
+ to_s == other.to_s
46
+ end
47
+
48
+ def eql?(other)
49
+ self == other
50
+ end
51
+
52
+ def hash
53
+ to_s.hash
54
+ end
55
+ end
56
+
57
+ module_function
58
+
59
+ def uri(arg)
60
+ URI.new(arg)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Artery
4
+ class Subscription
5
+ class IncomingMessage
6
+ attr_accessor :data, :reply, :from, :from_uri, :subscription, :options
7
+
8
+ def initialize(subscription, data, reply, from, **options)
9
+ @subscription = subscription
10
+ @data = data
11
+ @attributes = data[:attributes]
12
+ @reply = reply
13
+ @from = from
14
+ @from_uri = Routing.uri(@from)
15
+ @options = options
16
+ end
17
+
18
+ def action
19
+ from_uri.action
20
+ end
21
+
22
+ def index
23
+ data[:_index].to_i
24
+ end
25
+
26
+ def previous_index
27
+ data[:_previous_index].to_i
28
+ end
29
+
30
+ def has_index?
31
+ index.positive?
32
+ end
33
+
34
+ def from_updates?
35
+ options[:from_updates]
36
+ end
37
+
38
+ def update_by_us?
39
+ data[:updated_by_service].to_s == Artery.service_name.to_s
40
+ end
41
+
42
+ def enrich_data # rubocop:disable Metrics/AbcSize
43
+ # NO enrich needed as we already have message with attributes
44
+ if @attributes
45
+ yield @attributes
46
+ return
47
+ end
48
+
49
+ get_uri = Routing.uri service: from_uri.service,
50
+ model: from_uri.model,
51
+ plural: true,
52
+ action: :get
53
+ get_data = {
54
+ uuid: data[:uuid],
55
+ representation: subscription.representation_name
56
+ }
57
+
58
+ Artery.request get_uri.to_route, get_data do |on|
59
+ on.success do |attributes|
60
+ yield attributes
61
+ rescue Exception => e # rubocop:disable Lint/RescueException
62
+ error = Error.new("Error in subscription handler: #{e.inspect}",
63
+ original_exception: e,
64
+ subscription: {
65
+ subscriber: subscription.subscriber.to_s,
66
+ data: data.to_json,
67
+ route: from
68
+ },
69
+ request: { data: get_data.to_json, route: get_uri.to_route }, response: attributes.to_json)
70
+ Artery.handle_error error
71
+ end
72
+
73
+ on.error do |e|
74
+ if e.message == 'not_found'
75
+ yield(:not_found)
76
+ else
77
+ error = Error.new("Failed to get #{get_uri.model} from #{get_uri.service} with uuid='#{data[:uuid]}': #{e.message}",
78
+ e.artery_context.merge(subscription: {
79
+ subscriber: subscription.subscriber.to_s,
80
+ data: data.to_json,
81
+ route: from_uri.to_route
82
+ }))
83
+ Artery.handle_error error
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ def inspect
90
+ "<#{from}> <#{reply}> #{data}".inspect
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Artery
4
+ class Subscription
5
+ module Synchronization
6
+ extend ActiveSupport::Concern
7
+
8
+ ALIVE_EDGE = 2.minutes
9
+ HEARTBEAT_INTERVAL = 30.seconds
10
+
11
+ def synchronize?
12
+ options[:synchronize]
13
+ end
14
+
15
+ def synchronize_updates?
16
+ options[:synchronize_updates]
17
+ end
18
+
19
+ def synchronization_scope
20
+ options[:synchronize].is_a?(Hash) ? options[:synchronize][:scope] : nil
21
+ end
22
+
23
+ def synchronization_per_page
24
+ options[:synchronize].is_a?(Hash) ? options[:synchronize][:per_page] : nil
25
+ end
26
+
27
+ def synchronize_updates_scope
28
+ (options[:synchronize_updates].is_a?(Hash) && options[:synchronize_updates][:scope]) || synchronization_scope
29
+ end
30
+
31
+ def synchronize_updates_per_page
32
+ (options[:synchronize_updates].is_a?(Hash) && options[:synchronize_updates][:per_page]) || synchronization_per_page
33
+ end
34
+
35
+ def synchronize_updates_autoenrich?
36
+ options[:synchronize_updates].is_a?(Hash) ? options[:synchronize_updates][:autoenrich] : false
37
+ end
38
+
39
+ def synchronization_in_progress?
40
+ info.synchronization_in_progress? && synchronization_alive?
41
+ end
42
+
43
+ def synchronization_alive?
44
+ info.synchronization_heartbeat.blank? || (Time.zone.now - info.synchronization_heartbeat) < ALIVE_EDGE
45
+ end
46
+
47
+ def synchronization_in_progress!(val = true)
48
+ if val
49
+ Artery.synchronizing_subscriptions << self
50
+ run_synchronization_heartbeat
51
+
52
+ info.update! synchronization_in_progress: true, synchronization_heartbeat: Time.zone.now
53
+ else
54
+ Artery.synchronizing_subscriptions.delete self
55
+ stop_synchronization_heartbeat
56
+
57
+ info.update! synchronization_in_progress: false, synchronization_heartbeat: nil
58
+ end
59
+ end
60
+
61
+ def synchronization_transaction(&blk)
62
+ return unless blk
63
+ return info.synchronization_transaction(&blk) if info.respond_to?(:synchronization_transaction)
64
+
65
+ blk.call
66
+ end
67
+
68
+ def synchronization_page_update!(page)
69
+ info.update! synchronization_page: page
70
+ end
71
+
72
+ def synchronize!
73
+ return if uri.service == Artery.service_name || synchronization_in_progress?
74
+
75
+ if !new? && synchronize_updates?
76
+ receive_updates
77
+ elsif new? && synchronize?
78
+ receive_all
79
+ end
80
+ end
81
+
82
+ def receive_all
83
+ synchronization_in_progress! unless synchronization_in_progress?
84
+
85
+ while receive_all_once == :continue; end
86
+ end
87
+
88
+ def receive_updates
89
+ synchronization_in_progress!
90
+
91
+ while receive_updates_once == :continue; end
92
+ end
93
+
94
+ private
95
+
96
+ def run_synchronization_heartbeat
97
+ return if @heartbeat_thread
98
+
99
+ @heartbeat_thread = Thread.new do
100
+ while @heartbeat_thread
101
+ sleep HEARTBEAT_INTERVAL
102
+
103
+ info.update! synchronization_heartbeat: Time.zone.now
104
+ end
105
+ end
106
+ end
107
+
108
+ def stop_synchronization_heartbeat
109
+ @heartbeat_thread&.exit
110
+ end
111
+
112
+ def receive_all_once # rubocop:disable Metrics/AbcSize
113
+ should_continue = false
114
+ all_uri = Routing.uri(service: uri.service, model: uri.model, plural: true, action: :get_all)
115
+
116
+ page = info.synchronization_page ? info.synchronization_page + 1 : 0 if synchronization_per_page
117
+
118
+ objects = nil
119
+
120
+ all_data = {
121
+ representation: representation_name,
122
+ scope: synchronization_scope,
123
+ page: page,
124
+ per_page: synchronization_per_page
125
+ }
126
+
127
+ Artery.request all_uri.to_route, all_data do |on|
128
+ on.success do |data|
129
+ Artery.logger.debug "HEY-HEY, ALL OBJECTS: <#{all_uri.to_route}> #{[data].inspect}"
130
+
131
+ objects = data[:objects].map(&:with_indifferent_access)
132
+
133
+ synchronization_transaction { handler.call(:synchronization, objects, page) }
134
+
135
+ if synchronization_per_page && objects.count.positive?
136
+ synchronization_page_update!(page)
137
+ Artery.logger.debug "PAGE #{page} RECEIVED, WILL CONTINUE..."
138
+ should_continue = true
139
+ else
140
+ synchronization_page_update!(nil) if synchronization_per_page
141
+ synchronization_in_progress!(false)
142
+ update_info_by_message!(IncomingMessage.new(self, data, nil, all_uri.to_route))
143
+ end
144
+ rescue Exception => e
145
+ synchronization_in_progress!(false)
146
+ Artery.handle_error Error.new("Error in all objects request handling: #{e.inspect}",
147
+ original_exception: e,
148
+ request: {
149
+ route: all_uri.to_route,
150
+ data: all_data.to_json
151
+ },
152
+ response: data.to_json)
153
+ end
154
+
155
+ on.error do |e|
156
+ synchronization_in_progress!(false)
157
+ error = Error.new "Failed to get all objects #{uri.model} from #{uri.service} with scope='#{synchronization_scope}': " \
158
+ "#{e.message}", **e.artery_context
159
+ Artery.handle_error error
160
+ end
161
+ end
162
+ :continue if should_continue
163
+ end
164
+
165
+ def receive_updates_once # rubocop:disable Metrics/AbcSize
166
+ should_continue = false
167
+ updates_uri = Routing.uri(service: uri.service, model: uri.model, plural: true, action: :get_updates)
168
+ updates_data = {
169
+ after_index: latest_message_index
170
+ }
171
+
172
+ # Configurable autoenrich updates
173
+ if synchronize_updates_autoenrich?
174
+ # we must setup per_page as data is autoenriched and can be big
175
+ updates_data.merge! representation: representation_name,
176
+ scope: synchronize_updates_scope,
177
+ per_page: synchronize_updates_per_page
178
+ end
179
+
180
+ Artery.request updates_uri.to_route, updates_data do |on|
181
+ on.success do |data|
182
+ Artery.logger.debug "HEY-HEY, LAST_UPDATES: <#{updates_uri.to_route}> #{[data].inspect}"
183
+
184
+ updates = data[:updates].map(&:with_indifferent_access)
185
+ synchronization_transaction do
186
+ updates.sort_by { |u| u[:_index] }.each do |update|
187
+ from = Routing.uri(service: uri.service, model: uri.model, action: update.delete(:action)).to_route
188
+ handle(IncomingMessage.new(self, update, nil, from, from_updates: true))
189
+ end
190
+
191
+ update_info_by_message! IncomingMessage.new(self, data, nil, updates_uri.to_route)
192
+ end
193
+
194
+ if data[:_continue]
195
+ Artery.logger.debug 'NOT ALL UPDATES RECEIVED, WILL CONTINUE...'
196
+ should_continue = true
197
+ else
198
+ synchronization_in_progress!(false)
199
+ end
200
+ rescue Exception => e
201
+ synchronization_in_progress!(false)
202
+ Artery.handle_error Error.new("Error in updates request handling: #{e.inspect}",
203
+ original_exception: e,
204
+ request: {
205
+ route: updates_uri.to_route,
206
+ data: updates_data.to_json
207
+ },
208
+ response: data.to_json)
209
+ end
210
+
211
+ on.error do |e|
212
+ synchronization_in_progress!(false)
213
+ Artery.handle_error Error.new("Failed to get updates for #{uri.model} from #{uri.service}: #{e.message}",
214
+ **e.artery_context)
215
+ end
216
+ end
217
+ :continue if should_continue
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Artery
4
+ class Subscription
5
+ autoload :Synchronization, 'artery/subscription/synchronization'
6
+ autoload :IncomingMessage, 'artery/subscription/incoming_message'
7
+
8
+ include Synchronization
9
+
10
+ attr_accessor :uri, :subscriber, :handler, :options
11
+
12
+ DEFAULTS = {
13
+ synchronize: false,
14
+ synchronize_updates: true,
15
+ representation: Artery.service_name
16
+ }.freeze
17
+
18
+ def initialize(model, uri, handler:, **options)
19
+ @uri = uri
20
+ @subscriber = model
21
+ @handler = handler
22
+ @options = DEFAULTS.merge(options)
23
+
24
+ Artery.add_subscription self
25
+ end
26
+
27
+ def info
28
+ @info ||= Artery.subscription_info_class.find_for_subscription(self)
29
+ end
30
+
31
+ def representation_name
32
+ options[:representation]
33
+ end
34
+
35
+ def latest_message_index
36
+ info.latest_index.to_i
37
+ end
38
+
39
+ def source?
40
+ @subscriber.artery[:source]
41
+ end
42
+
43
+ def latest_outgoing_message_index
44
+ return unless source?
45
+
46
+ Artery.message_class.latest_index(@subscriber.artery_model_name)
47
+ end
48
+
49
+ def new?
50
+ !latest_message_index.positive?
51
+ end
52
+
53
+ def update_info_by_message!(message)
54
+ return if !message.has_index? || message.from_updates?
55
+
56
+ new_data = {}
57
+ new_data[:latest_index] = message.index if message.index.positive? && (message.index > latest_message_index)
58
+
59
+ info.update! new_data
60
+ end
61
+
62
+ def handle(message) # rubocop:disable Metrics/AbcSize
63
+ Artery.logger.push_tags message.reply
64
+ Artery.logger.debug "GOT MESSAGE: #{message.inspect}"
65
+
66
+ info.lock_for_message(message) do
67
+ if !message.from_updates? && synchronization_in_progress?
68
+ Artery.logger.debug 'SKIPPING MESSAGE RECEIVED WHILE SYNC IN PROGRESS'
69
+ return
70
+ end
71
+ return if !message.from_updates? && !validate_index(message)
72
+
73
+ if message.update_by_us?
74
+ Artery.logger.debug 'SKIPPING UPDATE MADE BY US'
75
+ update_info_by_message!(message)
76
+ return
77
+ end
78
+
79
+ unless handler.has_block?(message.action) || handler.has_block?(:_default)
80
+ Artery.logger.debug 'SKIPPING MESSAGE WE ARE NOT LISTENING TO'
81
+ update_info_by_message!(message)
82
+ return
83
+ end
84
+
85
+ case message.action
86
+ when :create, :update
87
+ message.enrich_data do |attributes|
88
+ handle_data(message, attributes)
89
+ end
90
+ else
91
+ handle_data(message)
92
+ end
93
+ end
94
+ ensure
95
+ Artery.logger.pop_tags
96
+ end
97
+
98
+ protected
99
+
100
+ def validate_index(message)
101
+ return true unless message.previous_index.positive? && latest_message_index.positive?
102
+
103
+ if message.previous_index > latest_message_index
104
+ Artery.logger.debug 'WE\'VE GOT FUTURE MESSAGE, REQUESTING ALL MISSED'
105
+
106
+ synchronize! # this will include current message
107
+ false
108
+ elsif message.previous_index < latest_message_index
109
+ Artery.logger.debug 'WE\'VE GOT PREVIOUS MESSAGE AND ALREADY HANDLED IT, SKIPPING'
110
+
111
+ false
112
+ else
113
+ true
114
+ end
115
+ end
116
+
117
+ def handle_data(message, data = nil)
118
+ data ||= message.data
119
+
120
+ info.lock_for_message(message) do
121
+ if data == :not_found # special data when enrich failed with not_found
122
+ Artery.logger.debug 'SKIP HANDLING MESSAGE BECAUSE ENRICH DATA IS BLANK'
123
+ else
124
+ handler.call(:_before_action, message.action, data, message.reply, message.from)
125
+
126
+ handler.call(message.action, data, message.reply, message.from) ||
127
+ handler.call(:_default, data, message.reply, message.from)
128
+
129
+ handler.call(:_after_action, message.action, data, message.reply, message.from)
130
+ end
131
+
132
+ update_info_by_message!(message)
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Artery
4
+ autoload :Subscription, 'artery/subscription'
5
+ autoload :HealthzSubscription, 'artery/healthz_subscription'
6
+ autoload :WorkerHealthzSubscription, 'artery/worker_healthz_subscription'
7
+
8
+ module Subscriptions
9
+ extend ActiveSupport::Concern
10
+ included do
11
+ class << self
12
+ attr_accessor :subscriptions
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ def subscriptions_on(*services)
18
+ services = services.flatten.map(&:to_sym)
19
+
20
+ subscriptions.slice(*subscriptions.keys.select { |uri| services.include?(uri.service) })
21
+ end
22
+
23
+ def add_subscription(subscription)
24
+ @subscriptions ||= {}
25
+ @subscriptions[subscription.uri] ||= []
26
+ @subscriptions[subscription.uri] << subscription
27
+ end
28
+
29
+ def synchronizing_subscriptions
30
+ @synchronizing_subscriptions ||= []
31
+ end
32
+
33
+ def clear_synchronizing_subscriptions!
34
+ Artery.synchronizing_subscriptions.dup.each do |s|
35
+ Artery.logger.warn "<#{s.subscriber}> [#{s.uri}] is still synchronizing, clearing.."
36
+
37
+ s.synchronization_in_progress!(false)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Artery
4
+ class Sync
5
+ attr_accessor :sync_id
6
+
7
+ def initialize(sync_id)
8
+ @sync_id = sync_id
9
+ end
10
+
11
+ def execute(services = nil)
12
+ services = Array.wrap(services).map(&:to_sym)
13
+ subscriptions_on_services = services.blank? ? Artery.subscriptions : Artery.subscriptions_on(services)
14
+
15
+ if subscriptions_on_services.blank?
16
+ Artery.logger.warn 'No suitable subscriptions defined, exiting...'
17
+ return
18
+ end
19
+
20
+ @sync_fiber = Fiber.new do # all synchronization inside must be synchronous
21
+ subscriptions_on_services.values.flatten.uniq.each(&:synchronize!)
22
+ end
23
+ @sync_fiber.resume
24
+ end
25
+
26
+ def self.run(subscriptions)
27
+ sync_id = SecureRandom.hex
28
+ Artery.logger.push_tags('Sync', sync_id)
29
+ Artery::Sync.new(sync_id).execute subscriptions
30
+ ensure
31
+ Artery.clear_synchronizing_subscriptions!
32
+ Artery.logger.pop_tags
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Artery
4
+ VERSION = '1.1.0'
5
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Artery
4
+ class Worker
5
+ class Error < Artery::Error; end
6
+
7
+ attr_reader :worker_id
8
+
9
+ def initialize
10
+ @worker_id = SecureRandom.hex
11
+ end
12
+
13
+ def subscribe_healthz
14
+ HealthzSubscription.new.subscribe
15
+ WorkerHealthzSubscription.new(worker_id, 'worker').subscribe
16
+ end
17
+
18
+ def run(services = nil)
19
+ services = Array.wrap(services).map(&:to_sym)
20
+ subscriptions_on_services = services.blank? ? Artery.subscriptions : Artery.subscriptions_on(services)
21
+
22
+ if subscriptions_on_services.blank?
23
+ Artery.logger.warn 'No suitable subscriptions defined, exiting...'
24
+ return
25
+ end
26
+
27
+ Artery.handle_signals
28
+
29
+ @sync = Artery::Sync.new worker_id
30
+
31
+ Artery.worker = self
32
+ Artery.start { worker_cycle(services, subscriptions_on_services) }
33
+ ensure
34
+ Artery.clear_synchronizing_subscriptions!
35
+ end
36
+
37
+ private
38
+
39
+ def worker_cycle(services, subscriptions_on_services)
40
+ Artery.logger.push_tags 'Worker', worker_id
41
+ tries = 0
42
+ begin
43
+ subscribe_healthz
44
+
45
+ @sync.execute services
46
+
47
+ subscriptions_on_services.each do |uri, subscriptions|
48
+ Artery.logger.debug "Subscribing on '#{uri}'"
49
+ Artery.subscribe uri.to_route, queue: "#{Artery.service_name}.worker" do |data, reply, from|
50
+ subscriptions.each do |subscription|
51
+ message = Subscription::IncomingMessage.new subscription, data, reply, from
52
+
53
+ subscription.handle(message)
54
+ rescue StandardError => e
55
+ Artery.handle_error Error.new("Error in subscription handling: #{e.inspect}",
56
+ original_exception: e,
57
+ subscription: {
58
+ subscriber: subscription.subscriber.to_s,
59
+ route: from,
60
+ data: data.to_json
61
+ })
62
+ end
63
+ end
64
+ end
65
+ rescue StandardError => e
66
+ tries += 1
67
+
68
+ Artery.handle_error Error.new("WORKER ERROR: #{e.inspect}", original_exception: e)
69
+ retry if tries <= 5
70
+
71
+ Artery.handle_error Error.new('Worker failed 5 times and exited.')
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Artery
4
+ class WorkerHealthzSubscription
5
+ attr_reader :id, :name
6
+
7
+ def initialize(id, name)
8
+ @id = id
9
+ @name = name
10
+ end
11
+
12
+ def subscribe
13
+ healthz_route = "#{Artery.service_name}.healthz.#{name}"
14
+ Artery.logger.debug "Subscribing on '#{healthz_route}' for #{name} #{id}"
15
+
16
+ Artery.subscribe healthz_route do |data, reply, _from|
17
+ next unless data['id'] == id
18
+
19
+ Artery.publish reply, status: :ok
20
+ end
21
+ end
22
+ end
23
+ end