eventhub-processor 0.2.3 → 0.3.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 +5 -13
- data/lib/eventhub/argument_parser.rb +2 -0
- data/lib/eventhub/base_exception.rb +2 -0
- data/lib/eventhub/configuration.rb +25 -26
- data/lib/eventhub/constant.rb +13 -13
- data/lib/eventhub/heartbeat.rb +77 -0
- data/lib/eventhub/helper.rb +49 -55
- data/lib/eventhub/message.rb +138 -128
- data/lib/eventhub/message_processor.rb +31 -0
- data/lib/eventhub/multi_logger.rb +89 -89
- data/lib/eventhub/no_deadletter_exception.rb +2 -0
- data/lib/eventhub/pidfile.rb +21 -0
- data/lib/eventhub/processor.rb +268 -307
- data/lib/eventhub/statistics.rb +47 -0
- data/lib/eventhub/test.rb +10 -0
- data/lib/eventhub/version.rb +1 -1
- data/lib/eventhub-processor.rb +36 -29
- metadata +47 -26
data/lib/eventhub/processor.rb
CHANGED
@@ -1,307 +1,268 @@
|
|
1
|
-
module EventHub
|
2
|
-
class Processor
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@
|
14
|
-
@
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
end
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
#
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
def stop_processor(restart=false)
|
271
|
-
@restart = restart
|
272
|
-
|
273
|
-
# close channels
|
274
|
-
[@channel_receiver,@channel_sender].each do |channel|
|
275
|
-
if channel
|
276
|
-
channel.close if channel.open?
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
# stop connection and event loop
|
281
|
-
if @connection
|
282
|
-
@connection.disconnect if @connection.connected?
|
283
|
-
EventMachine.stop if EventMachine.reactor_running?
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
def daemonize
|
288
|
-
EventHub.logger.info("Processor [#{@name}] is going to start as daemon")
|
289
|
-
|
290
|
-
# daemonize
|
291
|
-
Process.daemon
|
292
|
-
|
293
|
-
# write daemon pid
|
294
|
-
pids_folder = @folder + "/pids"
|
295
|
-
FileUtils.makedirs(pids_folder)
|
296
|
-
IO.write("#{pids_folder}/#{@name}.pid",Process.pid.to_s)
|
297
|
-
end
|
298
|
-
|
299
|
-
def post_start
|
300
|
-
# method which can be overwritten to call a code sequence after reactor start
|
301
|
-
end
|
302
|
-
|
303
|
-
def post_stop
|
304
|
-
end
|
305
|
-
|
306
|
-
end
|
307
|
-
end
|
1
|
+
module EventHub
|
2
|
+
class Processor
|
3
|
+
attr_reader :statistics, :name, :pidfile
|
4
|
+
|
5
|
+
include Helper
|
6
|
+
|
7
|
+
def version
|
8
|
+
"1.0.0"
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(name=nil)
|
12
|
+
@name = name || class_to_array(self.class)[1..-1].join(".")
|
13
|
+
@pidfile = EventHub::Pidfile.new(File.join(Dir.pwd, 'pids', "#{name}.pid"))
|
14
|
+
@statistics = EventHub::Statistics.new
|
15
|
+
@heartbeat = EventHub::Heartbeat.new(self)
|
16
|
+
@message_processor = EventHub::MessageProcessor.new(self)
|
17
|
+
|
18
|
+
@channel_receiver = nil
|
19
|
+
@channel_sender = nil
|
20
|
+
@restart = true
|
21
|
+
end
|
22
|
+
|
23
|
+
def configuration
|
24
|
+
EventHub::Configuration.instance.data
|
25
|
+
end
|
26
|
+
|
27
|
+
def server_host
|
28
|
+
configuration.get('server.host') || 'localhost'
|
29
|
+
end
|
30
|
+
|
31
|
+
def server_user
|
32
|
+
configuration.get('server.user') || 'admin'
|
33
|
+
end
|
34
|
+
|
35
|
+
def server_password
|
36
|
+
configuration.get('server.password') || 'admin'
|
37
|
+
end
|
38
|
+
|
39
|
+
def server_management_port
|
40
|
+
configuration.get('server.management_port') || 15672
|
41
|
+
end
|
42
|
+
|
43
|
+
def server_vhost
|
44
|
+
configuration.get('server.vhost') || 'event_hub'
|
45
|
+
end
|
46
|
+
|
47
|
+
def connection_settings
|
48
|
+
{ user: server_user, password: server_password, host: server_host, vhost: server_vhost }
|
49
|
+
end
|
50
|
+
|
51
|
+
def listener_queues
|
52
|
+
Array(
|
53
|
+
configuration.get('processor.listener_queue') ||
|
54
|
+
configuration.get('processor.listener_queues') ||
|
55
|
+
'undefined_listener_queues'
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def watchdog_cycle_in_s
|
60
|
+
configuration.get('processor.watchdog_cycle_is_s') || 15
|
61
|
+
end
|
62
|
+
|
63
|
+
def restart_in_s
|
64
|
+
configuration.get('processor.restart_in_s') || 15
|
65
|
+
end
|
66
|
+
|
67
|
+
def heartbeat_cycle_in_s
|
68
|
+
configuration.get('processor.heartbeat_cycle_in_s') || 300
|
69
|
+
end
|
70
|
+
|
71
|
+
def start(detached = false)
|
72
|
+
daemonize if detached
|
73
|
+
|
74
|
+
EventHub.logger.info("Processor [#{@name}] base folder [#{Dir.pwd}]")
|
75
|
+
|
76
|
+
# use timer here to have last heartbeat message working
|
77
|
+
Signal.trap("TERM") { EventMachine.add_timer(0) { about_to_stop } }
|
78
|
+
Signal.trap("INT") { EventMachine.add_timer(0) { about_to_stop } }
|
79
|
+
|
80
|
+
while @restart
|
81
|
+
begin
|
82
|
+
handle_start_internal
|
83
|
+
|
84
|
+
# custom post start method to be overwritten
|
85
|
+
post_start
|
86
|
+
|
87
|
+
rescue => e
|
88
|
+
id = EventHub.logger.save_detailed_error(e)
|
89
|
+
EventHub.logger.error("Unexpected exception: #{e}, see => #{id}. Trying to restart in #{self.restart_in_s} seconds...")
|
90
|
+
sleep_break self.restart_in_s
|
91
|
+
end
|
92
|
+
end # while
|
93
|
+
|
94
|
+
# custon post stop method to be overwritten
|
95
|
+
post_stop
|
96
|
+
|
97
|
+
EventHub.logger.info("Processor [#{@name}] has been stopped")
|
98
|
+
ensure
|
99
|
+
pidfile.delete
|
100
|
+
end
|
101
|
+
|
102
|
+
def handle_message(metadata, payload)
|
103
|
+
raise "Please implement method in derived class"
|
104
|
+
end
|
105
|
+
|
106
|
+
def watchdog
|
107
|
+
self.listener_queues.each do |queue_name|
|
108
|
+
begin
|
109
|
+
response = RestClient.get "http://#{self.server_user}:#{self.server_password}@#{self.server_host}:#{self.server_management_port}/api/queues/#{self.server_vhost}/#{queue_name}/bindings", { :content_type => :json}
|
110
|
+
data = JSON.parse(response.body)
|
111
|
+
|
112
|
+
if response.code != 200
|
113
|
+
EventHub.logger.warn("Watchdog: Server did not answered properly. Trying to restart in #{self.restart_in_s} seconds...")
|
114
|
+
EventMachine.add_timer(self.restart_in_s) { stop_processor(true) }
|
115
|
+
elsif data.size == 0
|
116
|
+
EventHub.logger.warn("Watchdog: Something is wrong with the vhost, queue [#{queue_name}], and/or bindings. Trying to restart in #{self.restart_in_s} seconds...")
|
117
|
+
EventMachine.add_timer(self.restart_in_s) { stop_processor(true) }
|
118
|
+
# does it make sence ? Needs maybe more checks in future
|
119
|
+
else
|
120
|
+
# Watchdog is happy :-)
|
121
|
+
# add timer for next check
|
122
|
+
EventMachine.add_timer(self.watchdog_cycle_in_s) { watchdog }
|
123
|
+
end
|
124
|
+
|
125
|
+
rescue => e
|
126
|
+
EventHub.logger.error("Watchdog: Unexpected exception: #{e}. Trying to restart in #{self.restart_in_s} seconds...")
|
127
|
+
stop_processor
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# send message
|
133
|
+
def send_message(message, exchange_name = EventHub::EH_X_INBOUND)
|
134
|
+
|
135
|
+
if @channel_sender.nil? || !@channel_sender.open?
|
136
|
+
@channel_sender = AMQP::Channel.new(@connection, prefetch: 1)
|
137
|
+
|
138
|
+
# use publisher confirm
|
139
|
+
@channel_sender.confirm_select
|
140
|
+
|
141
|
+
# @channel.on_error { |ch, channel_close| EventHub.logger.error "Oops! a channel-level exception: #{channel_close.reply_text}" }
|
142
|
+
# @channel.on_ack { |basic_ack| EventHub.logger.info "Received basic_ack: multiple = #{basic_ack.multiple}, delivery_tag = #{basic_ack.delivery_tag}" }
|
143
|
+
end
|
144
|
+
|
145
|
+
exchange = @channel_sender.direct(exchange_name, :durable => true, :auto_delete => false)
|
146
|
+
exchange.publish(message.to_json, :persistent => true)
|
147
|
+
end
|
148
|
+
|
149
|
+
def sleep_break(seconds) # breaks after n seconds or after interrupt
|
150
|
+
while (seconds > 0)
|
151
|
+
sleep(1)
|
152
|
+
seconds -= 1
|
153
|
+
break unless @restart
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def handle_start_internal
|
160
|
+
AMQP.start(self.connection_settings) do |connection, open_ok|
|
161
|
+
@connection = connection
|
162
|
+
|
163
|
+
handle_connection_loss
|
164
|
+
|
165
|
+
# create channel
|
166
|
+
@channel_receiver = AMQP::Channel.new(@connection, prefetch: 1)
|
167
|
+
|
168
|
+
self.listener_queues.each do |queue_name|
|
169
|
+
|
170
|
+
# connect to queue
|
171
|
+
queue = @channel_receiver.queue(queue_name, durable: true, auto_delete: false)
|
172
|
+
|
173
|
+
# subscribe to queue
|
174
|
+
queue.subscribe(:ack => true) do |metadata, payload|
|
175
|
+
begin
|
176
|
+
statistics.measure(payload.size) do
|
177
|
+
messages_to_send = @message_processor.process({ metadata: metadata, queue_name: queue_name}, payload)
|
178
|
+
|
179
|
+
# forward invalid or returned messages to dispatcher
|
180
|
+
messages_to_send.each do |message|
|
181
|
+
send_message(message)
|
182
|
+
end if messages_to_send
|
183
|
+
|
184
|
+
@channel_receiver.acknowledge(metadata.delivery_tag)
|
185
|
+
end
|
186
|
+
|
187
|
+
rescue EventHub::NoDeadletterException => e
|
188
|
+
@channel_receiver.reject(metadata.delivery_tag, true)
|
189
|
+
EventHub.logger.error("Unexpected exception in handle_message method: #{e}. Message will be requeued.")
|
190
|
+
EventHub.logger.save_detailed_error(e)
|
191
|
+
sleep_break self.restart_in_s
|
192
|
+
rescue => e
|
193
|
+
@channel_receiver.reject(metadata.delivery_tag, false)
|
194
|
+
EventHub.logger.error("Unexpected exception in handle_message method: #{e}. Message dead lettered.")
|
195
|
+
EventHub.logger.save_detailed_error(e,payload)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
EventHub.logger.info("Processor [#{@name}] is listening to vhost [#{self.server_vhost}], queues [#{self.listener_queues.join(", ")}]")
|
202
|
+
|
203
|
+
register_timers
|
204
|
+
|
205
|
+
# send first heartbeat
|
206
|
+
heartbeat
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def handle_connection_loss
|
211
|
+
@connection.on_tcp_connection_loss do |conn, settings|
|
212
|
+
EventHub.logger.warn("Processor lost tcp connection. Trying to restart in #{self.restart_in_s} seconds...")
|
213
|
+
stop_processor(true)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def register_timers
|
218
|
+
EventMachine.add_timer(watchdog_cycle_in_s) { watchdog }
|
219
|
+
EventMachine.add_periodic_timer(heartbeat_cycle_in_s) { heartbeat }
|
220
|
+
end
|
221
|
+
|
222
|
+
def heartbeat(action="running")
|
223
|
+
message = @heartbeat.build_message(action)
|
224
|
+
message.append_to_execution_history(@name)
|
225
|
+
send_message(message)
|
226
|
+
end
|
227
|
+
|
228
|
+
def about_to_stop
|
229
|
+
heartbeat("stopped")
|
230
|
+
stop_processor
|
231
|
+
end
|
232
|
+
|
233
|
+
def stop_processor(restart=false)
|
234
|
+
@restart = restart
|
235
|
+
|
236
|
+
# close channels
|
237
|
+
[@channel_receiver,@channel_sender].each do |channel|
|
238
|
+
if channel
|
239
|
+
channel.close if channel.open?
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# stop connection and event loop
|
244
|
+
if @connection
|
245
|
+
@connection.disconnect if @connection.connected?
|
246
|
+
EventMachine.stop if EventMachine.reactor_running?
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def daemonize
|
251
|
+
EventHub.logger.info("Processor [#{@name}] is going to start as daemon")
|
252
|
+
|
253
|
+
# daemonize
|
254
|
+
Process.daemon
|
255
|
+
|
256
|
+
pidfile.write(Process.pid.to_s)
|
257
|
+
end
|
258
|
+
|
259
|
+
def post_start
|
260
|
+
# method which can be overwritten to call a code sequence after reactor start
|
261
|
+
end
|
262
|
+
|
263
|
+
def post_stop
|
264
|
+
# method which can be overwritten to call a code sequence after reactor stop
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
268
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class EventHub::Statistics
|
2
|
+
attr_reader :messages_successful, :messages_unsuccessful, :messages_average_size, :messages_average_process_time
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@messages_successful = 0
|
6
|
+
@messages_unsuccessful = 0
|
7
|
+
@messages_average_size = 0
|
8
|
+
@messages_average_process_time = 0
|
9
|
+
@messages_total_process_time = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def measure(size, &block)
|
14
|
+
begin
|
15
|
+
start = Time.now
|
16
|
+
yield
|
17
|
+
success(Time.now - start, size)
|
18
|
+
rescue
|
19
|
+
failure
|
20
|
+
raise
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def success(process_time, size)
|
25
|
+
@messages_total_process_time += process_time
|
26
|
+
@messages_average_process_time = (messages_total_process_time + process_time) / (messages_successful + 1).to_f
|
27
|
+
@messages_average_size = (messages_total_size + size) / (messages_successful + 1).to_f
|
28
|
+
@messages_successful += 1
|
29
|
+
end
|
30
|
+
|
31
|
+
def failure
|
32
|
+
@messages_unsuccessful += 1
|
33
|
+
end
|
34
|
+
|
35
|
+
def messages_total
|
36
|
+
messages_unsuccessful + messages_successful
|
37
|
+
end
|
38
|
+
|
39
|
+
def messages_total_process_time
|
40
|
+
messages_average_process_time * messages_successful
|
41
|
+
end
|
42
|
+
|
43
|
+
def messages_total_size
|
44
|
+
messages_average_size * messages_successful
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/lib/eventhub/version.rb
CHANGED