brown 2.2.2.25.g85ddf08 → 2.2.2.27.gbc378e8

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