deimos-kafka 1.0.0.pre.beta15
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 +7 -0
- data/.circleci/config.yml +74 -0
- data/.gitignore +41 -0
- data/.gitmodules +0 -0
- data/.rspec +1 -0
- data/.rubocop.yml +321 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +9 -0
- data/CODE_OF_CONDUCT.md +77 -0
- data/Dockerfile +23 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +165 -0
- data/Guardfile +22 -0
- data/LICENSE.md +195 -0
- data/README.md +742 -0
- data/Rakefile +13 -0
- data/bin/deimos +4 -0
- data/deimos-kafka.gemspec +42 -0
- data/docker-compose.yml +71 -0
- data/docs/DATABASE_BACKEND.md +147 -0
- data/docs/PULL_REQUEST_TEMPLATE.md +34 -0
- data/lib/deimos.rb +134 -0
- data/lib/deimos/active_record_consumer.rb +81 -0
- data/lib/deimos/active_record_producer.rb +64 -0
- data/lib/deimos/avro_data_coder.rb +89 -0
- data/lib/deimos/avro_data_decoder.rb +36 -0
- data/lib/deimos/avro_data_encoder.rb +51 -0
- data/lib/deimos/backends/db.rb +27 -0
- data/lib/deimos/backends/kafka.rb +27 -0
- data/lib/deimos/backends/kafka_async.rb +27 -0
- data/lib/deimos/configuration.rb +88 -0
- data/lib/deimos/consumer.rb +164 -0
- data/lib/deimos/instrumentation.rb +71 -0
- data/lib/deimos/kafka_message.rb +27 -0
- data/lib/deimos/kafka_source.rb +126 -0
- data/lib/deimos/kafka_topic_info.rb +79 -0
- data/lib/deimos/message.rb +74 -0
- data/lib/deimos/metrics/datadog.rb +47 -0
- data/lib/deimos/metrics/mock.rb +39 -0
- data/lib/deimos/metrics/provider.rb +38 -0
- data/lib/deimos/monkey_patches/phobos_cli.rb +35 -0
- data/lib/deimos/monkey_patches/phobos_producer.rb +51 -0
- data/lib/deimos/monkey_patches/ruby_kafka_heartbeat.rb +85 -0
- data/lib/deimos/monkey_patches/schema_store.rb +19 -0
- data/lib/deimos/producer.rb +218 -0
- data/lib/deimos/publish_backend.rb +30 -0
- data/lib/deimos/railtie.rb +8 -0
- data/lib/deimos/schema_coercer.rb +108 -0
- data/lib/deimos/shared_config.rb +59 -0
- data/lib/deimos/test_helpers.rb +356 -0
- data/lib/deimos/tracing/datadog.rb +35 -0
- data/lib/deimos/tracing/mock.rb +40 -0
- data/lib/deimos/tracing/provider.rb +31 -0
- data/lib/deimos/utils/db_producer.rb +95 -0
- data/lib/deimos/utils/executor.rb +117 -0
- data/lib/deimos/utils/inline_consumer.rb +144 -0
- data/lib/deimos/utils/lag_reporter.rb +182 -0
- data/lib/deimos/utils/platform_schema_validation.rb +0 -0
- data/lib/deimos/utils/signal_handler.rb +68 -0
- data/lib/deimos/version.rb +5 -0
- data/lib/generators/deimos/db_backend/templates/migration +24 -0
- data/lib/generators/deimos/db_backend/templates/rails3_migration +30 -0
- data/lib/generators/deimos/db_backend_generator.rb +48 -0
- data/lib/tasks/deimos.rake +17 -0
- data/spec/active_record_consumer_spec.rb +81 -0
- data/spec/active_record_producer_spec.rb +107 -0
- data/spec/avro_data_decoder_spec.rb +18 -0
- data/spec/avro_data_encoder_spec.rb +37 -0
- data/spec/backends/db_spec.rb +35 -0
- data/spec/backends/kafka_async_spec.rb +11 -0
- data/spec/backends/kafka_spec.rb +11 -0
- data/spec/consumer_spec.rb +169 -0
- data/spec/deimos_spec.rb +117 -0
- data/spec/kafka_source_spec.rb +168 -0
- data/spec/kafka_topic_info_spec.rb +88 -0
- data/spec/phobos.bad_db.yml +73 -0
- data/spec/phobos.yml +73 -0
- data/spec/producer_spec.rb +397 -0
- data/spec/publish_backend_spec.rb +10 -0
- data/spec/schemas/com/my-namespace/MySchema-key.avsc +13 -0
- data/spec/schemas/com/my-namespace/MySchema.avsc +18 -0
- data/spec/schemas/com/my-namespace/MySchemaWithBooleans.avsc +18 -0
- data/spec/schemas/com/my-namespace/MySchemaWithDateTimes.avsc +33 -0
- data/spec/schemas/com/my-namespace/MySchemaWithId.avsc +28 -0
- data/spec/schemas/com/my-namespace/MySchemaWithUniqueId.avsc +32 -0
- data/spec/schemas/com/my-namespace/Widget.avsc +27 -0
- data/spec/schemas/com/my-namespace/WidgetTheSecond.avsc +27 -0
- data/spec/spec_helper.rb +207 -0
- data/spec/updateable_schema_store_spec.rb +36 -0
- data/spec/utils/db_producer_spec.rb +208 -0
- data/spec/utils/executor_spec.rb +42 -0
- data/spec/utils/lag_reporter_spec.rb +69 -0
- data/spec/utils/platform_schema_validation_spec.rb +0 -0
- data/spec/utils/signal_handler_spec.rb +16 -0
- data/support/deimos-solo.png +0 -0
- data/support/deimos-with-name-next.png +0 -0
- data/support/deimos-with-name.png +0 -0
- data/support/flipp-logo.png +0 -0
- metadata +452 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'deimos/tracing/provider'
|
4
|
+
|
5
|
+
module Deimos
|
6
|
+
module Tracing
|
7
|
+
# Tracing wrapper class for Datadog.
|
8
|
+
class Datadog < Tracing::Provider
|
9
|
+
# :nodoc:
|
10
|
+
def initialize(config)
|
11
|
+
raise 'Tracing config must specify service_name' if config[:service_name].nil?
|
12
|
+
|
13
|
+
@service = config[:service_name]
|
14
|
+
end
|
15
|
+
|
16
|
+
# :nodoc:
|
17
|
+
def start(span_name, options={})
|
18
|
+
span = ::Datadog.tracer.trace(span_name)
|
19
|
+
span.service = @service
|
20
|
+
span.resource = options[:resource]
|
21
|
+
span
|
22
|
+
end
|
23
|
+
|
24
|
+
# :nodoc:
|
25
|
+
def finish(span)
|
26
|
+
span.finish
|
27
|
+
end
|
28
|
+
|
29
|
+
# :nodoc:
|
30
|
+
def set_error(span, exception)
|
31
|
+
span.set_error(exception)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'deimos/tracing/provider'
|
4
|
+
|
5
|
+
module Deimos
|
6
|
+
module Tracing
|
7
|
+
# Class that mocks out tracing functionality
|
8
|
+
class Mock < Tracing::Provider
|
9
|
+
# :nodoc:
|
10
|
+
def initialize(logger=nil)
|
11
|
+
@logger = logger || Logger.new(STDOUT)
|
12
|
+
@logger.info('MockTracingProvider initialized')
|
13
|
+
end
|
14
|
+
|
15
|
+
# :nodoc:
|
16
|
+
def start(span_name, _options={})
|
17
|
+
@logger.info("Mock span '#{span_name}' started")
|
18
|
+
{
|
19
|
+
name: span_name,
|
20
|
+
started_at: Time.zone.now
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
# :nodoc:
|
25
|
+
def finish(span)
|
26
|
+
name = span[:name]
|
27
|
+
start = span[:started_at]
|
28
|
+
finish = Time.zone.now
|
29
|
+
@logger.info("Mock span '#{name}' finished: #{start} to #{finish}")
|
30
|
+
end
|
31
|
+
|
32
|
+
# :nodoc:
|
33
|
+
def set_error(span, exception)
|
34
|
+
span[:exception] = exception
|
35
|
+
name = span[:name]
|
36
|
+
@logger.info("Mock span '#{name}' set an error: #{exception}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
4
|
+
module Deimos
|
5
|
+
module Tracing
|
6
|
+
# Base class for all tracing providers.
|
7
|
+
class Provider
|
8
|
+
# Returns a span object and starts the trace.
|
9
|
+
# @param span_name [String] The name of the span/trace
|
10
|
+
# @param options [Hash] Options for the span
|
11
|
+
# @return [Object] The span object
|
12
|
+
def start(span_name, options={})
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
|
16
|
+
# Finishes the trace on the span object.
|
17
|
+
# @param span [Object] The span to finish trace on
|
18
|
+
def finish(span)
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
# Set an error on the span.
|
23
|
+
# @param span [Object] The span to set error on
|
24
|
+
# @param exception [Exception] The exception that occurred
|
25
|
+
def set_error(span, exception)
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Deimos
|
4
|
+
module Utils
|
5
|
+
# Class which continually polls the database and sends Kafka messages.
|
6
|
+
class DbProducer
|
7
|
+
include Phobos::Producer
|
8
|
+
attr_accessor :id, :current_topic
|
9
|
+
|
10
|
+
BATCH_SIZE = 1000
|
11
|
+
|
12
|
+
# @param logger [Logger]
|
13
|
+
def initialize(logger=Logger.new(STDOUT))
|
14
|
+
@id = SecureRandom.uuid
|
15
|
+
@logger = logger
|
16
|
+
@logger.push_tags("DbProducer #{@id}") if @logger.respond_to?(:push_tags)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Start the poll.
|
20
|
+
def start
|
21
|
+
@logger.info('Starting...')
|
22
|
+
@signal_to_stop = false
|
23
|
+
loop do
|
24
|
+
if @signal_to_stop
|
25
|
+
@logger.info('Shutting down')
|
26
|
+
break
|
27
|
+
end
|
28
|
+
process_next_messages
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Stop the poll.
|
33
|
+
def stop
|
34
|
+
@logger.info('Received signal to stop')
|
35
|
+
@signal_to_stop = true
|
36
|
+
end
|
37
|
+
|
38
|
+
# Complete one loop of processing all messages in the DB.
|
39
|
+
def process_next_messages
|
40
|
+
topics = retrieve_topics
|
41
|
+
@logger.info("Found topics: #{topics}")
|
42
|
+
topics.each(&method(:process_topic))
|
43
|
+
sleep(0.5)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Array<String>]
|
47
|
+
def retrieve_topics
|
48
|
+
KafkaMessage.select('distinct topic').map(&:topic).uniq
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param topic [String]
|
52
|
+
# @return [String] the topic that was locked, or nil if none were.
|
53
|
+
def process_topic(topic)
|
54
|
+
# If the topic is already locked, another producer is currently
|
55
|
+
# working on it. Move on to the next one.
|
56
|
+
unless KafkaTopicInfo.lock(topic, @id)
|
57
|
+
@logger.debug("Could not lock topic #{topic} - continuing")
|
58
|
+
return
|
59
|
+
end
|
60
|
+
@current_topic = topic
|
61
|
+
messages = retrieve_messages
|
62
|
+
|
63
|
+
while messages.any?
|
64
|
+
produce_messages(messages.map(&:phobos_message))
|
65
|
+
messages.first.class.where(id: messages.map(&:id)).delete_all
|
66
|
+
break if messages.size < BATCH_SIZE
|
67
|
+
|
68
|
+
KafkaTopicInfo.heartbeat(@current_topic, @id) # keep alive
|
69
|
+
messages = retrieve_messages
|
70
|
+
end
|
71
|
+
KafkaTopicInfo.clear_lock(@current_topic, @id)
|
72
|
+
rescue StandardError => e
|
73
|
+
@logger.error("Error processing messages for topic #{@current_topic}: #{e.class.name}: #{e.message} #{e.backtrace.join("\n")}")
|
74
|
+
KafkaTopicInfo.register_error(@current_topic, @id)
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [Array<KafkaMessage>]
|
78
|
+
def retrieve_messages
|
79
|
+
KafkaMessage.where(topic: @current_topic).order(:id).limit(BATCH_SIZE)
|
80
|
+
end
|
81
|
+
|
82
|
+
# @param batch [Array<Hash>]
|
83
|
+
def produce_messages(batch)
|
84
|
+
@logger.debug("Publishing #{batch.size} messages to #{@current_topic}")
|
85
|
+
producer.publish_list(batch)
|
86
|
+
Deimos.metrics&.increment(
|
87
|
+
'publish',
|
88
|
+
tags: %W(status:success topic:#{@current_topic}),
|
89
|
+
by: batch.size
|
90
|
+
)
|
91
|
+
@logger.info("Sent #{batch.size} messages to #{@current_topic}")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Lint/RescueException
|
4
|
+
module Deimos
|
5
|
+
module Utils
|
6
|
+
# Mostly copied from Phobos::Executor. We should DRY this up by putting in a
|
7
|
+
# PR to make it more generic. Might even make sense to move to a separate
|
8
|
+
# gem.
|
9
|
+
class Executor
|
10
|
+
# @return [Array<#start, #stop, #id>]
|
11
|
+
attr_accessor :runners
|
12
|
+
|
13
|
+
# @param runners [Array<#start, #stop, #id>] A list of objects that can be
|
14
|
+
# started or stopped.
|
15
|
+
# @param logger [Logger]
|
16
|
+
def initialize(runners, logger=Logger.new(STDOUT))
|
17
|
+
@threads = Concurrent::Array.new
|
18
|
+
@runners = runners
|
19
|
+
@logger = logger
|
20
|
+
end
|
21
|
+
|
22
|
+
# Start the executor.
|
23
|
+
def start
|
24
|
+
@logger.info('Starting executor')
|
25
|
+
@signal_to_stop = false
|
26
|
+
@threads.clear
|
27
|
+
@thread_pool = Concurrent::FixedThreadPool.new(@runners.size)
|
28
|
+
|
29
|
+
@runners.each do |runner|
|
30
|
+
@thread_pool.post do
|
31
|
+
thread = Thread.current
|
32
|
+
thread.abort_on_exception = true
|
33
|
+
@threads << thread
|
34
|
+
run_object(runner)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
# Stop the executor.
|
42
|
+
def stop
|
43
|
+
return if @signal_to_stop
|
44
|
+
|
45
|
+
@logger.info('Stopping executor')
|
46
|
+
@signal_to_stop = true
|
47
|
+
@runners.each(&:stop)
|
48
|
+
@threads.select(&:alive?).each do |thread|
|
49
|
+
begin
|
50
|
+
thread.wakeup
|
51
|
+
rescue StandardError
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
@thread_pool&.shutdown
|
56
|
+
@thread_pool&.wait_for_termination
|
57
|
+
@logger.info('Executor stopped')
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# @param exception [Throwable]
|
63
|
+
# @return [Hash]
|
64
|
+
def error_metadata(exception)
|
65
|
+
{
|
66
|
+
exception_class: exception.class.name,
|
67
|
+
exception_message: exception.message,
|
68
|
+
backtrace: exception.backtrace
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def run_object(runner)
|
73
|
+
retry_count = 0
|
74
|
+
|
75
|
+
begin
|
76
|
+
@logger.info("Running #{runner.id}")
|
77
|
+
runner.start
|
78
|
+
retry_count = 0 # success - reset retry count
|
79
|
+
rescue Exception => e
|
80
|
+
handle_crashed_runner(runner, e, retry_count)
|
81
|
+
retry_count += 1
|
82
|
+
retry unless @signal_to_stop
|
83
|
+
end
|
84
|
+
rescue Exception => e
|
85
|
+
@logger.error("Failed to run listener (#{e.message}) #{error_metadata(e)}")
|
86
|
+
raise e
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [ExponentialBackoff]
|
90
|
+
def create_exponential_backoff
|
91
|
+
min = 1
|
92
|
+
max = 60
|
93
|
+
ExponentialBackoff.new(min, max).tap do |backoff|
|
94
|
+
backoff.randomize_factor = rand
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# When "runner#start" is interrupted / crashes we assume it's
|
99
|
+
# safe to be called again
|
100
|
+
def handle_crashed_runner(runner, error, retry_count)
|
101
|
+
backoff = create_exponential_backoff
|
102
|
+
interval = backoff.interval_at(retry_count).round(2)
|
103
|
+
|
104
|
+
metadata = {
|
105
|
+
listener_id: runner.id,
|
106
|
+
retry_count: retry_count,
|
107
|
+
waiting_time: interval
|
108
|
+
}.merge(error_metadata(error))
|
109
|
+
|
110
|
+
@logger.error("Runner crashed, waiting #{interval}s (#{error.message}) #{metadata}")
|
111
|
+
sleep(interval)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# rubocop:enable Lint/RescueException
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Class to consume messages. Can be used with integration testing frameworks.
|
4
|
+
# Assumes that you have a topic with only one partition.
|
5
|
+
module Deimos
|
6
|
+
module Utils
|
7
|
+
# Listener that can seek to get the last X messages in a topic.
|
8
|
+
class SeekListener < Phobos::Listener
|
9
|
+
attr_accessor :num_messages
|
10
|
+
|
11
|
+
# :nodoc:
|
12
|
+
def start_listener
|
13
|
+
@num_messages ||= 10
|
14
|
+
@consumer = create_kafka_consumer
|
15
|
+
@consumer.subscribe(topic, @subscribe_opts)
|
16
|
+
|
17
|
+
begin
|
18
|
+
last_offset = @kafka_client.last_offset_for(topic, 0)
|
19
|
+
offset = last_offset - num_messages
|
20
|
+
if offset.positive?
|
21
|
+
Deimos.config.logger.info("Seeking to #{offset}")
|
22
|
+
@consumer.seek(topic, 0, offset)
|
23
|
+
end
|
24
|
+
rescue StandardError => e
|
25
|
+
"Could not seek to offset: #{e.message}"
|
26
|
+
end
|
27
|
+
|
28
|
+
instrument('listener.start_handler', listener_metadata) do
|
29
|
+
@handler_class.start(@kafka_client)
|
30
|
+
end
|
31
|
+
log_info('Listener started', listener_metadata)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Class to return the messages consumed.
|
36
|
+
class MessageBankHandler < Deimos::Consumer
|
37
|
+
include Phobos::Handler
|
38
|
+
|
39
|
+
cattr_accessor :total_messages
|
40
|
+
|
41
|
+
# @param klass [Class < Deimos::Consumer]
|
42
|
+
def self.config_class=(klass)
|
43
|
+
self.config.merge!(klass.config)
|
44
|
+
end
|
45
|
+
|
46
|
+
# :nodoc:
|
47
|
+
def self.start(_kafka_client)
|
48
|
+
self.total_messages = []
|
49
|
+
end
|
50
|
+
|
51
|
+
# :nodoc:
|
52
|
+
def consume(payload, metadata)
|
53
|
+
puts "Got #{payload}"
|
54
|
+
self.class.total_messages << {
|
55
|
+
key: metadata[:key],
|
56
|
+
payload: payload
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Class which can process/consume messages inline.
|
62
|
+
class InlineConsumer
|
63
|
+
MAX_MESSAGE_WAIT_TIME = 1.second
|
64
|
+
MAX_TOPIC_WAIT_TIME = 10.seconds
|
65
|
+
|
66
|
+
# Get the last X messages from a topic. You can specify a subclass of
|
67
|
+
# Deimos::Consumer or Deimos::Producer, or provide the
|
68
|
+
# schema, namespace and key_config directly.
|
69
|
+
# @param topic [String]
|
70
|
+
# @param config_class [Class < Deimos::Consumer|Deimos::Producer>]
|
71
|
+
# @param schema [String]
|
72
|
+
# @param namespace [String]
|
73
|
+
# @param key_config [Hash]
|
74
|
+
# @param num_messages [Number]
|
75
|
+
# @return [Array<Hash>]
|
76
|
+
def self.get_messages_for(topic:, schema: nil, namespace: nil, key_config: nil,
|
77
|
+
config_class: nil, num_messages: 10)
|
78
|
+
if config_class
|
79
|
+
MessageBankHandler.config_class = config_class
|
80
|
+
elsif schema.nil? || key_config.nil?
|
81
|
+
raise 'You must specify either a config_class or a schema, namespace and key_config!'
|
82
|
+
else
|
83
|
+
MessageBankHandler.class_eval do
|
84
|
+
schema schema
|
85
|
+
namespace namespace
|
86
|
+
key_config key_config
|
87
|
+
@decoder = nil
|
88
|
+
@key_decoder = nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
self.consume(topic: topic,
|
92
|
+
frk_consumer: MessageBankHandler,
|
93
|
+
num_messages: num_messages)
|
94
|
+
messages = MessageBankHandler.total_messages
|
95
|
+
messages.size <= num_messages ? messages : messages[-num_messages..-1]
|
96
|
+
end
|
97
|
+
|
98
|
+
# Consume the last X messages from a topic.
|
99
|
+
# @param topic [String]
|
100
|
+
# @param frk_consumer [Class]
|
101
|
+
# @param num_messages [Integer] If this number is >= the number
|
102
|
+
# of messages in the topic, all messages will be consumed.
|
103
|
+
def self.consume(topic:, frk_consumer:, num_messages: 10)
|
104
|
+
listener = SeekListener.new(
|
105
|
+
handler: frk_consumer,
|
106
|
+
group_id: SecureRandom.hex,
|
107
|
+
topic: topic,
|
108
|
+
heartbeat_interval: 1
|
109
|
+
)
|
110
|
+
listener.num_messages = num_messages
|
111
|
+
|
112
|
+
# Add the start_time and last_message_time attributes to the
|
113
|
+
# consumer class so we can kill it if it's gone on too long
|
114
|
+
class << frk_consumer
|
115
|
+
attr_accessor :start_time, :last_message_time
|
116
|
+
end
|
117
|
+
|
118
|
+
subscribers = []
|
119
|
+
subscribers << ActiveSupport::Notifications.
|
120
|
+
subscribe('phobos.listener.process_message') do
|
121
|
+
frk_consumer.last_message_time = Time.zone.now
|
122
|
+
end
|
123
|
+
subscribers << ActiveSupport::Notifications.
|
124
|
+
subscribe('phobos.listener.start_handler') do
|
125
|
+
frk_consumer.start_time = Time.zone.now
|
126
|
+
frk_consumer.last_message_time = nil
|
127
|
+
end
|
128
|
+
subscribers << ActiveSupport::Notifications.
|
129
|
+
subscribe('heartbeat.consumer.kafka') do
|
130
|
+
if frk_consumer.last_message_time
|
131
|
+
if Time.zone.now - frk_consumer.last_message_time > MAX_MESSAGE_WAIT_TIME
|
132
|
+
raise Phobos::AbortError
|
133
|
+
end
|
134
|
+
elsif Time.zone.now - frk_consumer.start_time > MAX_TOPIC_WAIT_TIME
|
135
|
+
Deimos.config.logger.error('Aborting - initial wait too long')
|
136
|
+
raise Phobos::AbortError
|
137
|
+
end
|
138
|
+
end
|
139
|
+
listener.start
|
140
|
+
subscribers.each { |s| ActiveSupport::Notifications.unsubscribe(s) }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|