waterdrop 2.6.6 → 2.6.8
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci.yml +16 -9
- data/CHANGELOG.md +20 -7
- data/Gemfile.lock +8 -8
- data/README.md +19 -500
- data/certs/cert_chain.pem +21 -21
- data/docker-compose.yml +19 -12
- data/lib/waterdrop/clients/buffered.rb +59 -3
- data/lib/waterdrop/clients/dummy.rb +20 -0
- data/lib/waterdrop/clients/rdkafka.rb +6 -1
- data/lib/waterdrop/config.rb +5 -0
- data/lib/waterdrop/contracts/message.rb +4 -1
- data/lib/waterdrop/errors.rb +3 -0
- data/lib/waterdrop/instrumentation/callbacks/delivery.rb +6 -4
- data/lib/waterdrop/instrumentation/logger_listener.rb +21 -1
- data/lib/waterdrop/instrumentation/notifications.rb +5 -0
- data/lib/waterdrop/producer/async.rb +4 -2
- data/lib/waterdrop/producer/sync.rb +4 -2
- data/lib/waterdrop/producer/transactions.rb +165 -0
- data/lib/waterdrop/producer.rb +54 -3
- data/lib/waterdrop/version.rb +1 -1
- data/waterdrop.gemspec +3 -3
- data.tar.gz.sig +0 -0
- metadata +29 -28
- metadata.gz.sig +0 -0
@@ -19,17 +19,73 @@ module WaterDrop
|
|
19
19
|
super
|
20
20
|
@messages = []
|
21
21
|
@topics = Hash.new { |k, v| k[v] = [] }
|
22
|
+
|
23
|
+
@transaction_mutex = Mutex.new
|
24
|
+
@transaction_active = false
|
25
|
+
@transaction_messages = []
|
26
|
+
@transaction_topics = Hash.new { |k, v| k[v] = [] }
|
27
|
+
@transaction_level = 0
|
22
28
|
end
|
23
29
|
|
24
30
|
# "Produces" message to Kafka: it acknowledges it locally, adds it to the internal buffer
|
25
31
|
# @param message [Hash] `WaterDrop::Producer#produce_sync` message hash
|
26
32
|
def produce(message)
|
27
|
-
|
28
|
-
|
29
|
-
|
33
|
+
if @transaction_active
|
34
|
+
@transaction_topics[message.fetch(:topic)] << message
|
35
|
+
@transaction_messages << message
|
36
|
+
else
|
37
|
+
# We pre-validate the message payload, so topic is ensured to be present
|
38
|
+
@topics[message.fetch(:topic)] << message
|
39
|
+
@messages << message
|
40
|
+
end
|
41
|
+
|
30
42
|
SyncResponse.new
|
31
43
|
end
|
32
44
|
|
45
|
+
# Yields the code pretending it is in a transaction
|
46
|
+
# Supports our aborting transaction flow
|
47
|
+
# Moves messages the appropriate buffers only if transaction is successful
|
48
|
+
def transaction
|
49
|
+
@transaction_level += 1
|
50
|
+
|
51
|
+
return yield if @transaction_mutex.owned?
|
52
|
+
|
53
|
+
@transaction_mutex.lock
|
54
|
+
@transaction_active = true
|
55
|
+
|
56
|
+
result = nil
|
57
|
+
commit = false
|
58
|
+
|
59
|
+
catch(:abort) do
|
60
|
+
result = yield
|
61
|
+
commit = true
|
62
|
+
end
|
63
|
+
|
64
|
+
commit || raise(WaterDrop::Errors::AbortTransaction)
|
65
|
+
|
66
|
+
# Transfer transactional data on success
|
67
|
+
@transaction_topics.each do |topic, messages|
|
68
|
+
@topics[topic] += messages
|
69
|
+
end
|
70
|
+
|
71
|
+
@messages += @transaction_messages
|
72
|
+
|
73
|
+
result
|
74
|
+
rescue StandardError => e
|
75
|
+
return if e.is_a?(WaterDrop::Errors::AbortTransaction)
|
76
|
+
|
77
|
+
raise
|
78
|
+
ensure
|
79
|
+
@transaction_level -= 1
|
80
|
+
|
81
|
+
if @transaction_level.zero? && @transaction_mutex.owned?
|
82
|
+
@transaction_topics.clear
|
83
|
+
@transaction_messages.clear
|
84
|
+
@transaction_active = false
|
85
|
+
@transaction_mutex.unlock
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
33
89
|
# Returns messages produced to a given topic
|
34
90
|
# @param topic [String]
|
35
91
|
def messages_for(topic)
|
@@ -25,6 +25,26 @@ module WaterDrop
|
|
25
25
|
true
|
26
26
|
end
|
27
27
|
|
28
|
+
# Yields the code pretending it is in a transaction
|
29
|
+
# Supports our aborting transaction flow
|
30
|
+
def transaction
|
31
|
+
result = nil
|
32
|
+
commit = false
|
33
|
+
|
34
|
+
catch(:abort) do
|
35
|
+
result = yield
|
36
|
+
commit = true
|
37
|
+
end
|
38
|
+
|
39
|
+
commit || raise(WaterDrop::Errors::AbortTransaction)
|
40
|
+
|
41
|
+
result
|
42
|
+
rescue StandardError => e
|
43
|
+
return if e.is_a?(WaterDrop::Errors::AbortTransaction)
|
44
|
+
|
45
|
+
raise
|
46
|
+
end
|
47
|
+
|
28
48
|
# @param _args [Object] anything really, this dummy is suppose to support anything
|
29
49
|
# @return [self] returns self for chaining cases
|
30
50
|
def method_missing(*_args)
|
@@ -11,7 +11,9 @@ module WaterDrop
|
|
11
11
|
# @param producer [WaterDrop::Producer] producer instance with its config, etc
|
12
12
|
# @note We overwrite this that way, because we do not care
|
13
13
|
def new(producer)
|
14
|
-
|
14
|
+
config = producer.config.kafka.to_h
|
15
|
+
|
16
|
+
client = ::Rdkafka::Config.new(config).producer
|
15
17
|
|
16
18
|
# This callback is not global and is per client, thus we do not have to wrap it with a
|
17
19
|
# callbacks manager to make it work
|
@@ -20,6 +22,9 @@ module WaterDrop
|
|
20
22
|
producer.config.monitor
|
21
23
|
)
|
22
24
|
|
25
|
+
# Switch to the transactional mode if user provided the transactional id
|
26
|
+
client.init_transactions if config.key?(:'transactional.id')
|
27
|
+
|
23
28
|
client
|
24
29
|
end
|
25
30
|
end
|
data/lib/waterdrop/config.rb
CHANGED
@@ -64,6 +64,11 @@ module WaterDrop
|
|
64
64
|
# option [Numeric] how many seconds should we wait with the backoff on queue having space for
|
65
65
|
# more messages before re-raising the error.
|
66
66
|
setting :wait_timeout_on_queue_full, default: 10
|
67
|
+
|
68
|
+
setting :wait_backoff_on_transaction_command, default: 0.5
|
69
|
+
|
70
|
+
setting :max_attempts_on_transaction_command, default: 5
|
71
|
+
|
67
72
|
# option [Boolean] should we send messages. Setting this to false can be really useful when
|
68
73
|
# testing and or developing because when set to false, won't actually ping Kafka but will
|
69
74
|
# run all the validations, etc
|
@@ -26,7 +26,10 @@ module WaterDrop
|
|
26
26
|
@max_payload_size = max_payload_size
|
27
27
|
end
|
28
28
|
|
29
|
-
required(:topic)
|
29
|
+
required(:topic) do |val|
|
30
|
+
(val.is_a?(String) || val.is_a?(Symbol)) && TOPIC_REGEXP.match?(val.to_s)
|
31
|
+
end
|
32
|
+
|
30
33
|
required(:payload) { |val| val.nil? || val.is_a?(String) }
|
31
34
|
optional(:key) { |val| val.nil? || (val.is_a?(String) && !val.empty?) }
|
32
35
|
optional(:partition) { |val| val.is_a?(Integer) && val >= -1 }
|
data/lib/waterdrop/errors.rb
CHANGED
@@ -32,6 +32,9 @@ module WaterDrop
|
|
32
32
|
# Raised when there is an inline error during single message produce operations
|
33
33
|
ProduceError = Class.new(BaseError)
|
34
34
|
|
35
|
+
# Raise it within a transaction to abort it
|
36
|
+
AbortTransaction = Class.new(BaseError)
|
37
|
+
|
35
38
|
# Raised when during messages producing something bad happened inline
|
36
39
|
class ProduceManyError < ProduceError
|
37
40
|
attr_reader :dispatched
|
@@ -17,10 +17,10 @@ module WaterDrop
|
|
17
17
|
# Emits delivery details to the monitor
|
18
18
|
# @param delivery_report [Rdkafka::Producer::DeliveryReport] delivery report
|
19
19
|
def call(delivery_report)
|
20
|
-
if delivery_report.error.to_i.
|
21
|
-
instrument_error(delivery_report)
|
22
|
-
else
|
20
|
+
if delivery_report.error.to_i.zero?
|
23
21
|
instrument_acknowledged(delivery_report)
|
22
|
+
else
|
23
|
+
instrument_error(delivery_report)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -36,6 +36,7 @@ module WaterDrop
|
|
36
36
|
offset: delivery_report.offset,
|
37
37
|
partition: delivery_report.partition,
|
38
38
|
topic: delivery_report.topic_name,
|
39
|
+
delivery_report: delivery_report,
|
39
40
|
type: 'librdkafka.dispatch_error'
|
40
41
|
)
|
41
42
|
end
|
@@ -47,7 +48,8 @@ module WaterDrop
|
|
47
48
|
producer_id: @producer_id,
|
48
49
|
offset: delivery_report.offset,
|
49
50
|
partition: delivery_report.partition,
|
50
|
-
topic: delivery_report.topic_name
|
51
|
+
topic: delivery_report.topic_name,
|
52
|
+
delivery_report: delivery_report
|
51
53
|
)
|
52
54
|
end
|
53
55
|
end
|
@@ -112,9 +112,14 @@ module WaterDrop
|
|
112
112
|
debug(event, messages)
|
113
113
|
end
|
114
114
|
|
115
|
+
# @param event [Dry::Events::Event] event that happened with the details
|
116
|
+
def on_buffer_purged(event)
|
117
|
+
info(event, 'Successfully purging buffer')
|
118
|
+
end
|
119
|
+
|
115
120
|
# @param event [Dry::Events::Event] event that happened with the details
|
116
121
|
def on_producer_closed(event)
|
117
|
-
info
|
122
|
+
info(event, 'Closing producer')
|
118
123
|
end
|
119
124
|
|
120
125
|
# @param event [Dry::Events::Event] event that happened with the error details
|
@@ -125,6 +130,21 @@ module WaterDrop
|
|
125
130
|
error(event, "Error occurred: #{error} - #{type}")
|
126
131
|
end
|
127
132
|
|
133
|
+
# @param event [Dry::Events::Event] event that happened with the details
|
134
|
+
def on_transaction_started(event)
|
135
|
+
info(event, 'Starting transaction')
|
136
|
+
end
|
137
|
+
|
138
|
+
# @param event [Dry::Events::Event] event that happened with the details
|
139
|
+
def on_transaction_aborted(event)
|
140
|
+
info(event, 'Aborting transaction')
|
141
|
+
end
|
142
|
+
|
143
|
+
# @param event [Dry::Events::Event] event that happened with the details
|
144
|
+
def on_transaction_committed(event)
|
145
|
+
info(event, 'Committing transaction')
|
146
|
+
end
|
147
|
+
|
128
148
|
private
|
129
149
|
|
130
150
|
# @return [Boolean] should we report the messages details in the debug mode.
|
@@ -60,8 +60,10 @@ module WaterDrop
|
|
60
60
|
producer_id: id,
|
61
61
|
messages: messages
|
62
62
|
) do
|
63
|
-
|
64
|
-
|
63
|
+
with_transaction_if_transactional do
|
64
|
+
messages.each do |message|
|
65
|
+
dispatched << produce(message)
|
66
|
+
end
|
65
67
|
end
|
66
68
|
|
67
69
|
dispatched
|
@@ -63,8 +63,10 @@ module WaterDrop
|
|
63
63
|
dispatched = []
|
64
64
|
|
65
65
|
@monitor.instrument('messages.produced_sync', producer_id: id, messages: messages) do
|
66
|
-
|
67
|
-
|
66
|
+
with_transaction_if_transactional do
|
67
|
+
messages.each do |message|
|
68
|
+
dispatched << produce(message)
|
69
|
+
end
|
68
70
|
end
|
69
71
|
|
70
72
|
dispatched.map! do |handler|
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WaterDrop
|
4
|
+
class Producer
|
5
|
+
# Transactions related producer functionalities
|
6
|
+
module Transactions
|
7
|
+
# Creates a transaction.
|
8
|
+
#
|
9
|
+
# Karafka transactions work in a similar manner to SQL db transactions though there are some
|
10
|
+
# crucial differences. When you start a transaction, all messages produced during it will
|
11
|
+
# be delivered together or will fail together. The difference is, that messages from within
|
12
|
+
# a single transaction can be delivered and will have a delivery handle but will be then
|
13
|
+
# compacted prior to moving the LSO forward. This means, that not every delivery handle for
|
14
|
+
# async dispatches will emit a queue purge error. None for sync as the delivery has happened
|
15
|
+
# but they will never be visible by the transactional consumers.
|
16
|
+
#
|
17
|
+
# Transactions **are** thread-safe however they lock a mutex. This means, that for
|
18
|
+
# high-throughput transactional messages production in multiple threads
|
19
|
+
# (for example in Karafka), it may be much better to use few instances that can work in
|
20
|
+
# parallel.
|
21
|
+
#
|
22
|
+
# Please note, that if a producer is configured as transactional, it **cannot** produce
|
23
|
+
# messages outside of transactions, that is why by default all dispatches will be wrapped
|
24
|
+
# with a transaction. One transaction per single dispatch and for `produce_many` it will be
|
25
|
+
# a single transaction wrapping all messages dispatches (not one per message).
|
26
|
+
#
|
27
|
+
# @return Block result
|
28
|
+
#
|
29
|
+
# @example Simple transaction
|
30
|
+
# producer.transaction do
|
31
|
+
# producer.produce_async(topic: 'topic', payload: 'data')
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# @example Aborted transaction - messages producer won't be visible by consumers
|
35
|
+
# producer.transaction do
|
36
|
+
# producer.produce_sync(topic: 'topic', payload: 'data')
|
37
|
+
# throw(:abort)
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# @example Use block result last handler to wait on all messages ack
|
41
|
+
# handler = producer.transaction do
|
42
|
+
# producer.produce_async(topic: 'topic', payload: 'data')
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# handler.wait
|
46
|
+
def transaction
|
47
|
+
# This will safely allow us to support one operation transactions so a transactional
|
48
|
+
# producer can work without the transactional block if needed
|
49
|
+
return yield if @transaction_mutex.owned?
|
50
|
+
|
51
|
+
@transaction_mutex.synchronize do
|
52
|
+
transactional_instrument(:committed) do
|
53
|
+
with_transactional_error_handling(:begin) do
|
54
|
+
transactional_instrument(:started) { client.begin_transaction }
|
55
|
+
end
|
56
|
+
|
57
|
+
result = nil
|
58
|
+
commit = false
|
59
|
+
|
60
|
+
catch(:abort) do
|
61
|
+
result = yield
|
62
|
+
commit = true
|
63
|
+
end
|
64
|
+
|
65
|
+
commit || raise(WaterDrop::Errors::AbortTransaction)
|
66
|
+
|
67
|
+
with_transactional_error_handling(:commit) do
|
68
|
+
client.commit_transaction
|
69
|
+
end
|
70
|
+
|
71
|
+
result
|
72
|
+
rescue StandardError => e
|
73
|
+
with_transactional_error_handling(:abort) do
|
74
|
+
transactional_instrument(:aborted) { client.abort_transaction }
|
75
|
+
end
|
76
|
+
|
77
|
+
raise unless e.is_a?(WaterDrop::Errors::AbortTransaction)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Boolean] Is this producer a transactional one
|
83
|
+
def transactional?
|
84
|
+
return @transactional if instance_variable_defined?(:'@transactional')
|
85
|
+
|
86
|
+
@transactional = config.kafka.to_h.key?(:'transactional.id')
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Runs provided code with a transaction wrapper if transactions are enabled.
|
92
|
+
# This allows us to simplify the async and sync batch dispatchers because we can ensure that
|
93
|
+
# their internal dispatches will be wrapped only with a single transaction and not
|
94
|
+
# a transaction per message
|
95
|
+
# @param block [Proc] code we want to run
|
96
|
+
def with_transaction_if_transactional(&block)
|
97
|
+
transactional? ? transaction(&block) : yield
|
98
|
+
end
|
99
|
+
|
100
|
+
# Instruments the transactional operation with producer id
|
101
|
+
#
|
102
|
+
# @param key [Symbol] transaction operation key
|
103
|
+
# @param block [Proc] block to run inside the instrumentation or nothing if not given
|
104
|
+
def transactional_instrument(key, &block)
|
105
|
+
@monitor.instrument("transaction.#{key}", producer_id: id, &block)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Error handling for transactional operations is a bit special. There are three types of
|
109
|
+
# errors coming from librdkafka:
|
110
|
+
# - retryable - indicates that a given operation (like offset commit) can be retried after
|
111
|
+
# a backoff and that is should be operating later as expected. We try to retry those
|
112
|
+
# few times before finally failing.
|
113
|
+
# - fatal - errors that will not recover no matter what (for example being fenced out)
|
114
|
+
# - abortable - error from which we cannot recover but for which we should abort the
|
115
|
+
# current transaction.
|
116
|
+
#
|
117
|
+
# The code below handles this logic also publishing the appropriate notifications via our
|
118
|
+
# notifications pipeline.
|
119
|
+
#
|
120
|
+
# @param action [Symbol] action type
|
121
|
+
# @param allow_abortable [Boolean] should we allow for the abortable flow. This is set to
|
122
|
+
# false internally to prevent attempts to abort from failed abort operations
|
123
|
+
def with_transactional_error_handling(action, allow_abortable: true)
|
124
|
+
attempt ||= 0
|
125
|
+
attempt += 1
|
126
|
+
|
127
|
+
yield
|
128
|
+
rescue ::Rdkafka::RdkafkaError => e
|
129
|
+
# Decide if there is a chance to retry given error
|
130
|
+
do_retry = e.retryable? && attempt < config.max_attempts_on_transaction_command
|
131
|
+
|
132
|
+
@monitor.instrument(
|
133
|
+
'error.occurred',
|
134
|
+
producer_id: id,
|
135
|
+
caller: self,
|
136
|
+
error: e,
|
137
|
+
type: "transaction.#{action}",
|
138
|
+
retry: do_retry,
|
139
|
+
attempt: attempt
|
140
|
+
)
|
141
|
+
|
142
|
+
raise if e.fatal?
|
143
|
+
|
144
|
+
if do_retry
|
145
|
+
# Backoff more and more before retries
|
146
|
+
sleep(config.wait_backoff_on_transaction_command * attempt)
|
147
|
+
|
148
|
+
retry
|
149
|
+
end
|
150
|
+
|
151
|
+
if e.abortable? && allow_abortable
|
152
|
+
# Always attempt to abort but if aborting fails with an abortable error, do not attempt
|
153
|
+
# to abort from abort as this could create an infinite loop
|
154
|
+
with_transactional_error_handling(:abort, allow_abortable: false) do
|
155
|
+
transactional_instrument(:aborted) { @client.abort_transaction }
|
156
|
+
end
|
157
|
+
|
158
|
+
raise
|
159
|
+
end
|
160
|
+
|
161
|
+
raise
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
data/lib/waterdrop/producer.rb
CHANGED
@@ -7,6 +7,7 @@ module WaterDrop
|
|
7
7
|
include Sync
|
8
8
|
include Async
|
9
9
|
include Buffer
|
10
|
+
include Transactions
|
10
11
|
include ::Karafka::Core::Helpers::Time
|
11
12
|
|
12
13
|
# Which of the inline flow errors do we want to intercept and re-bind
|
@@ -38,6 +39,7 @@ module WaterDrop
|
|
38
39
|
@buffer_mutex = Mutex.new
|
39
40
|
@connecting_mutex = Mutex.new
|
40
41
|
@operating_mutex = Mutex.new
|
42
|
+
@transaction_mutex = Mutex.new
|
41
43
|
|
42
44
|
@status = Status.new
|
43
45
|
@messages = Concurrent::Array.new
|
@@ -117,8 +119,25 @@ module WaterDrop
|
|
117
119
|
@client
|
118
120
|
end
|
119
121
|
|
122
|
+
# Purges data from both the buffer queue as well as the librdkafka queue.
|
123
|
+
#
|
124
|
+
# @note This is an operation that can cause data loss. Keep that in mind. It will not only
|
125
|
+
# purge the internal WaterDrop buffer but will also purge the librdkafka queue as well as
|
126
|
+
# will cancel any outgoing messages dispatches.
|
127
|
+
def purge
|
128
|
+
@monitor.instrument('buffer.purged', producer_id: id) do
|
129
|
+
@buffer_mutex.synchronize do
|
130
|
+
@messages = Concurrent::Array.new
|
131
|
+
end
|
132
|
+
|
133
|
+
@client.purge
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
120
137
|
# Flushes the buffers in a sync way and closes the producer
|
121
|
-
|
138
|
+
# @param force [Boolean] should we force closing even with outstanding messages after the
|
139
|
+
# max wait timeout
|
140
|
+
def close(force: false)
|
122
141
|
@operating_mutex.synchronize do
|
123
142
|
return unless @status.active?
|
124
143
|
|
@@ -149,7 +168,26 @@ module WaterDrop
|
|
149
168
|
# We also mark it as closed only if it was connected, if not, it would trigger a new
|
150
169
|
# connection that anyhow would be immediately closed
|
151
170
|
if @client
|
152
|
-
|
171
|
+
# Why do we trigger it early instead of just having `#close` do it?
|
172
|
+
# The linger.ms time will be ignored for the duration of the call,
|
173
|
+
# queued messages will be sent to the broker as soon as possible.
|
174
|
+
begin
|
175
|
+
# `max_wait_timeout` is in seconds at the moment
|
176
|
+
@client.flush(@config.max_wait_timeout * 1_000) unless @client.closed?
|
177
|
+
# We can safely ignore timeouts here because any left outstanding requests
|
178
|
+
# will anyhow force wait on close if not forced.
|
179
|
+
# If forced, we will purge the queue and just close
|
180
|
+
rescue ::Rdkafka::RdkafkaError, Rdkafka::AbstractHandle::WaitTimeoutError
|
181
|
+
nil
|
182
|
+
ensure
|
183
|
+
# Purge fully the local queue in case of a forceful shutdown just to be sure, that
|
184
|
+
# there are no dangling messages. In case flush was successful, there should be
|
185
|
+
# none but we do it just in case it timed out
|
186
|
+
purge if force
|
187
|
+
end
|
188
|
+
|
189
|
+
@client.close
|
190
|
+
|
153
191
|
@client = nil
|
154
192
|
end
|
155
193
|
|
@@ -162,6 +200,11 @@ module WaterDrop
|
|
162
200
|
end
|
163
201
|
end
|
164
202
|
|
203
|
+
# Closes the producer with forced close after timeout, purging any outgoing data
|
204
|
+
def close!
|
205
|
+
close(force: true)
|
206
|
+
end
|
207
|
+
|
165
208
|
private
|
166
209
|
|
167
210
|
# Ensures that we don't run any operations when the producer is not configured or when it
|
@@ -211,7 +254,15 @@ module WaterDrop
|
|
211
254
|
ensure_active!
|
212
255
|
end
|
213
256
|
|
214
|
-
|
257
|
+
# In case someone defines topic as a symbol, we need to convert it into a string as
|
258
|
+
# librdkafka does not accept symbols
|
259
|
+
message = message.merge(topic: message[:topic].to_s) if message[:topic].is_a?(Symbol)
|
260
|
+
|
261
|
+
if transactional?
|
262
|
+
transaction { client.produce(**message) }
|
263
|
+
else
|
264
|
+
client.produce(**message)
|
265
|
+
end
|
215
266
|
rescue SUPPORTED_FLOW_ERRORS.first => e
|
216
267
|
# Unless we want to wait and retry and it's a full queue, we raise normally
|
217
268
|
raise unless @config.wait_on_queue_full
|
data/lib/waterdrop/version.rb
CHANGED
data/waterdrop.gemspec
CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.description = spec.summary
|
17
17
|
spec.license = 'MIT'
|
18
18
|
|
19
|
-
spec.add_dependency 'karafka-core', '>= 2.
|
19
|
+
spec.add_dependency 'karafka-core', '>= 2.2.3', '< 3.0.0'
|
20
20
|
spec.add_dependency 'zeitwerk', '~> 2.3'
|
21
21
|
|
22
22
|
if $PROGRAM_NAME.end_with?('gem')
|
@@ -31,10 +31,10 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.metadata = {
|
32
32
|
'funding_uri' => 'https://karafka.io/#become-pro',
|
33
33
|
'homepage_uri' => 'https://karafka.io',
|
34
|
-
'changelog_uri' => 'https://
|
34
|
+
'changelog_uri' => 'https://karafka.io/docs/Changelog-WaterDrop',
|
35
35
|
'bug_tracker_uri' => 'https://github.com/karafka/waterdrop/issues',
|
36
36
|
'source_code_uri' => 'https://github.com/karafka/waterdrop',
|
37
|
-
'documentation_uri' => 'https://
|
37
|
+
'documentation_uri' => 'https://karafka.io/docs/#waterdrop',
|
38
38
|
'rubygems_mfa_required' => 'true'
|
39
39
|
}
|
40
40
|
end
|
data.tar.gz.sig
CHANGED
Binary file
|