brown 2.2.2.25.g85ddf08 → 2.2.2.27.gbc378e8

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
  SHA256:
3
- metadata.gz: 9a28012ee7ec217616a14b03b2fc61827d7f813b34d50e4a847ac6ab7a0e0303
4
- data.tar.gz: a8671a1393a9e9c89a00eb8f949ebb901b6607fae694fe697dac0a771c4cff98
3
+ metadata.gz: 37896a92c6f9b1be0a84ed693b67fb57108bd2f1b97b27676f5f0912babe435b
4
+ data.tar.gz: 9613d3ee171cacd4eb004c82acfe837d2e3ce757d8bb2d63fba3c45cd9237664
5
5
  SHA512:
6
- metadata.gz: 3d375959226721589ad39fbca4087bbe360a1394739a1689746b5715c34403b5594d6a3270f47a557dc46d58ba407be0f9917a71d9f015a3140db5cf9f3acd6e
7
- data.tar.gz: ac06d3e484d71111faa20d4ca761ba97f0525e68d5ba25769725e8a6d9db2efd108a59a38e52242da3e9e62b959a894ecf2f61865c6bffcbc018c544b575603d
6
+ metadata.gz: af6720e99c528fc3d57d03f3c208853ec6edb177f29940235811f6713ff8f50585c4f745e6d6a206d7e75750c74c5ad7a8e79ee21b1bce887cc020d462b3f335
7
+ data.tar.gz: f7f2eee6381f9046e7f5e8ff2d0f5d71725eb62ceca37580830d590ac2b4790f4b3aa5fa173b5d9dbe28a18ad4f488411f73bb57a2dd1a2048218e41cf0a9b58
@@ -109,6 +109,12 @@ module Brown::Agent::AMQP::ClassMethods
109
109
  # bindings are already in place, and thus no declarations should be
110
110
  # made to the AMQP broker.
111
111
  #
112
+ # @param reject_on_error [Boolean] by default, if an uncaught exception
113
+ # is raised during message handling, an error will be logged and the
114
+ # listener will not process any new messages. However, if
115
+ # `reject_on_error: true`, then instead the message will be rejected, and
116
+ # processing will continue
117
+ #
112
118
  # @param blk [Proc] is called every time a message is received from
113
119
  # the queue, and an instance of {Brown::Agent::AMQPMessage} will
114
120
  # be passed as the sole argument.
@@ -123,6 +129,7 @@ module Brown::Agent::AMQP::ClassMethods
123
129
  allowed_classes: nil,
124
130
  routing_key: nil,
125
131
  predeclared: false,
132
+ reject_on_error: false,
126
133
  &blk
127
134
  )
128
135
  exchange_list = (Array === exchange_name ? exchange_name : [exchange_name]).map(&:to_s)
@@ -142,6 +149,7 @@ module Brown::Agent::AMQP::ClassMethods
142
149
  routing_key: routing_key,
143
150
  callback: blk,
144
151
  predeclared: predeclared,
152
+ reject_on_error: reject_on_error,
145
153
  }
146
154
  end
147
155
  end
@@ -58,13 +58,26 @@ module Brown::Agent::AMQP::Initializer
58
58
  def initialize_listeners
59
59
  (self.class.amqp_listeners || []).each do |listener|
60
60
  logger.debug(logloc) { "Initializing AMQP listener #{listener}" }
61
- worker_method = "amqp_listener_worker_#{SecureRandom.uuid}".to_sym
61
+ worker_method = "amqp_listener_worker_#{SecureRandom.uuid}".to_sym
62
+ wrapper_method = "amqp_listener_wrapper_#{SecureRandom.uuid}".to_sym
63
+
62
64
  define_singleton_method(worker_method, listener[:callback])
65
+ define_singleton_method(wrapper_method) do |msg|
66
+ begin
67
+ __send__(worker_method, msg)
68
+ rescue StandardError => ex
69
+ log_exception(ex) { "Exception while processing message #{msg.payload.inspect}" }
70
+ if listener[:reject_on_error]
71
+ logger.info(logloc) { "Rejecting message which caused exception" }
72
+ msg.reject
73
+ end
74
+ end
75
+ end
63
76
 
64
77
  @stimuli ||= []
65
78
  @stimuli << {
66
79
  name: "amqp_listener_#{listener[:exchange_list].join("_").gsub(/[^A-Za-z0-9_]/, '_').gsub(/__+/, "_")}",
67
- method: method(worker_method),
80
+ method: method(wrapper_method),
68
81
  stimuli_proc: proc do |worker|
69
82
  consumer = queue(listener).subscribe(manual_ack: true) do |di, prop, payload|
70
83
  if listener[:autoparse]
@@ -0,0 +1,34 @@
1
+ require 'logger'
2
+ require 'rb-inotify'
3
+ require 'securerandom'
4
+
5
+ # Class-level inotify support for Brown agents.
6
+ #
7
+ # These methods are intended to be applied to a `Brown::Agent` subclass, so you
8
+ # can use them to define new file watchers in your agent.
9
+ # You should not attempt to extend your classes directly with this module; the
10
+ # {Brown::Agent::FileWatch} module should handle that for you automatically.
11
+ #
12
+ module Brown::Agent::FileWatch::ClassMethods
13
+ attr_reader :file_watchers
14
+
15
+ # Watch one or more files or directories for modification.
16
+ #
17
+ # If a change is detected on any of the paths you list, then the associated
18
+ # block of code will be executed. If a path is a directory, then the entire
19
+ # directory hierarchy will be watched recursively for any file or directory
20
+ # creation, deletion, renaming, ormodification. If the path is a file, it
21
+ # will be watched for modification. In either case, the path being
22
+ # specified must already exist.
23
+ #
24
+ # @yieldparam event [INotify::Event] the raw `INotify::Event` that
25
+ # caused the block to be called.
26
+ #
27
+ def watch(*paths, &blk)
28
+ @watchers ||= []
29
+ @watchers << {
30
+ paths: paths,
31
+ callback: blk,
32
+ }
33
+ end
34
+ end
@@ -0,0 +1,140 @@
1
+ require "securerandom"
2
+ require "json"
3
+ require "yaml"
4
+
5
+ # Methods that have to be prepended in order to work properly.
6
+ #
7
+ module Brown::Agent::AMQP::Initializer
8
+ def initialize(*_)
9
+ begin
10
+ super
11
+ rescue ArgumentError => ex
12
+ if ex.message =~ /wrong number of arguments.*expected 0/
13
+ super()
14
+ else
15
+ raise
16
+ end
17
+ end
18
+
19
+ initialize_publishers
20
+ end
21
+
22
+ def run
23
+ initialize_listeners
24
+
25
+ super
26
+ end
27
+
28
+ def shutdown
29
+ amqp_session.close
30
+
31
+ super
32
+ end
33
+
34
+ private
35
+
36
+ def amqp_session
37
+ @amqp_session ||= begin
38
+ logger.debug(logloc) { "Initializing AMQP session" }
39
+ Bunny.new(config.amqp_url, recover_from_connection_close: true, logger: config.logger).tap do |session|
40
+ session.on_blocked { |blocked| logger.warn(logloc) { "AMQP connection has become blocked: #{blocked.reason}" } }
41
+ session.on_unblocked { logger.info(logloc) { "AMQP connection has unblocked" } }
42
+ session.start
43
+ end
44
+ end
45
+ end
46
+
47
+ def initialize_publishers
48
+ (self.class.amqp_publishers || []).each do |publisher|
49
+ logger.debug(logloc) { "Initializing AMQP publisher #{publisher}" }
50
+ opts = { exchange_name: publisher[:name] }.merge(publisher[:opts])
51
+
52
+ amqp_publisher = Brown::Agent::AMQPPublisher.new(amqp_session: amqp_session, **opts)
53
+
54
+ define_singleton_method(publisher[:name]) { amqp_publisher }
55
+ end
56
+ end
57
+
58
+ def initialize_listeners
59
+ (self.class.amqp_listeners || []).each do |listener|
60
+ logger.debug(logloc) { "Initializing AMQP listener #{listener}" }
61
+ worker_method = "amqp_listener_worker_#{SecureRandom.uuid}".to_sym
62
+ define_singleton_method(worker_method, listener[:callback])
63
+
64
+ @stimuli ||= []
65
+ @stimuli << {
66
+ name: "amqp_listener_#{listener[:exchange_list].join("_").gsub(/[^A-Za-z0-9_]/, '_').gsub(/__+/, "_")}",
67
+ method: method(worker_method),
68
+ stimuli_proc: proc do |worker|
69
+ consumer = queue(listener).subscribe(manual_ack: true) do |di, prop, payload|
70
+ if listener[:autoparse]
71
+ logger.debug(logloc) { "Attempting to autoparse against Content-Type: #{prop.content_type.inspect}" }
72
+ case prop.content_type
73
+ when "application/json"
74
+ logger.debug(logloc) { "Parsing as JSON" }
75
+ payload = JSON.parse(payload)
76
+ when "application/x.yaml"
77
+ logger.debug(logloc) { "Parsing as YAML" }
78
+ payload = YAML.load(payload)
79
+ when "application/vnd.brown.object.v1"
80
+ logger.debug(logloc) { "Parsing as Brown object, allowed classes: #{listener[:allowed_classes]}" }
81
+ begin
82
+ payload = YAML.safe_load(payload, listener[:allowed_classes])
83
+ rescue Psych::DisallowedClass => ex
84
+ logger.error(logloc) { "message rejected: #{ex.message}" }
85
+ di.channel.nack(di.delivery_tag, false, false)
86
+ next
87
+ end
88
+ end
89
+ end
90
+
91
+ worker.call Brown::Agent::AMQPMessage.new(di, prop, payload)
92
+ end
93
+
94
+ while consumer&.channel&.status == :open do
95
+ logger.debug(logloc) { "stimuli_proc for #{listener[:queue_name]} having a snooze" }
96
+ sleep
97
+ end
98
+ end
99
+ }
100
+ end
101
+ end
102
+
103
+ def queue(listener)
104
+ @queue_cache ||= {}
105
+ @queue_cache[listener] ||= begin
106
+ bind_queue(
107
+ queue_name: listener[:queue_name],
108
+ exchange_list: listener[:exchange_list].map(&:to_s),
109
+ concurrency: listener[:concurrency],
110
+ )
111
+ rescue StandardError => ex
112
+ log_exception(ex) { "Unknown error while binding queue #{listener[:queue_name].inspect} to exchange list #{listener[:exchange_list].inspect}" }
113
+ sleep 5
114
+ retry
115
+ end
116
+ end
117
+
118
+ def bind_queue(queue_name:, exchange_list:, concurrency:)
119
+ ch = amqp_session.create_channel
120
+ ch.prefetch(concurrency)
121
+
122
+ ch.queue(queue_name, durable: true).tap do |q|
123
+ exchange_list.each do |exchange_name|
124
+ if exchange_name != ""
125
+ begin
126
+ q.bind(exchange_name)
127
+ rescue Bunny::NotFound => ex
128
+ logger.error { "bind failed: #{ex.message}" }
129
+ sleep 5
130
+ return bind_queue(
131
+ queue_name: queue_name,
132
+ exchange_list: exchange_list,
133
+ concurrency: concurrency
134
+ )
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brown
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.2.25.g85ddf08
4
+ version: 2.2.2.27.gbc378e8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Palmer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-22 00:00:00.000000000 Z
11
+ date: 2020-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -203,6 +203,8 @@ files:
203
203
  - lib/brown/agent/amqp_message_mock.rb
204
204
  - lib/brown/agent/amqp_publisher.rb
205
205
  - lib/brown/agent/class_methods.rb
206
+ - lib/brown/agent/inotify/class_methods.rb
207
+ - lib/brown/agent/inotify/initializer.rb
206
208
  - lib/brown/agent/memo.rb
207
209
  - lib/brown/agent/stimulus.rb
208
210
  - lib/brown/agent/stimulus/metrics.rb