karafka-rdkafka 0.13.5 → 0.13.6
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 +5 -1
- data/CHANGELOG.md +6 -0
- data/docker-compose.yml +16 -15
- data/lib/rdkafka/abstract_handle.rb +4 -3
- data/lib/rdkafka/admin/create_partitions_handle.rb +6 -3
- data/lib/rdkafka/admin/create_topic_handle.rb +6 -3
- data/lib/rdkafka/admin/delete_topic_handle.rb +6 -3
- data/lib/rdkafka/bindings.rb +23 -2
- data/lib/rdkafka/consumer.rb +35 -39
- data/lib/rdkafka/error.rb +60 -1
- data/lib/rdkafka/metadata.rb +4 -4
- data/lib/rdkafka/producer/delivery_handle.rb +1 -1
- data/lib/rdkafka/producer.rb +81 -2
- data/lib/rdkafka/version.rb +1 -1
- data/spec/rdkafka/admin/create_topic_handle_spec.rb +4 -2
- data/spec/rdkafka/admin/delete_topic_handle_spec.rb +3 -1
- data/spec/rdkafka/admin_spec.rb +1 -1
- data/spec/rdkafka/metadata_spec.rb +2 -2
- data/spec/rdkafka/producer_spec.rb +279 -8
- data/spec/spec_helper.rb +2 -0
- data.tar.gz.sig +0 -0
- metadata +2 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4da2961103e92a94c36f605359cc5380ffa6d740cdc667329797ce9345caa663
|
4
|
+
data.tar.gz: 419aff211c5bfe35c244549fa14e042e92a39ad8583af2b8844283b7f1dca5b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c0b0d1069163ece1dcf5734ebc80e952a86c3eccc96885f856dc3f4569a0aaddc12db28fda7927d4c360b6f2ba0078a782f7ee6582554159c0e0b80c975e03bf
|
7
|
+
data.tar.gz: 2e3ba6a6ef850bafa305f2d9f2a9d3b4268fedbafc50f2b5ce2fedffcb4bb018b8a1ed5b1ce1b3a1efbad51621a2ad74d448641774d93879da628c06d4a1260c
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/.github/workflows/ci.yml
CHANGED
@@ -20,6 +20,7 @@ jobs:
|
|
20
20
|
fail-fast: false
|
21
21
|
matrix:
|
22
22
|
ruby:
|
23
|
+
- '3.3.0-preview2'
|
23
24
|
- '3.2'
|
24
25
|
- '3.1'
|
25
26
|
- '3.1.0'
|
@@ -36,6 +37,10 @@ jobs:
|
|
36
37
|
- name: Install package dependencies
|
37
38
|
run: "[ -e $APT_DEPS ] || sudo apt-get install -y --no-install-recommends $APT_DEPS"
|
38
39
|
|
40
|
+
- name: Start Kafka with docker-compose
|
41
|
+
run: |
|
42
|
+
docker-compose up -d || (sleep 5 && docker-compose up -d)
|
43
|
+
|
39
44
|
- name: Set up Ruby
|
40
45
|
uses: ruby/setup-ruby@v1
|
41
46
|
with:
|
@@ -47,7 +52,6 @@ jobs:
|
|
47
52
|
GITHUB_COVERAGE: ${{matrix.coverage}}
|
48
53
|
|
49
54
|
run: |
|
50
|
-
docker-compose up -d --no-recreate
|
51
55
|
bundle install --path vendor/bundle
|
52
56
|
cd ext && bundle exec rake && cd ..
|
53
57
|
bundle exec rspec
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# 0.13.6 (2023-10-17)
|
2
|
+
* **[Feature]** Support transactions API in the producer
|
3
|
+
* [Enhancement] Add `raise_response_error` flag to the `Rdkafka::AbstractHandle`.
|
4
|
+
* [Enhancement] Provide `#purge` to remove any outstanding requests from the producer.
|
5
|
+
* [Enhancement] Fix `#flush` does not handle the timeouts errors by making it return true if all flushed or false if failed. We do **not** raise an exception here to keep it backwards compatible.
|
6
|
+
|
1
7
|
# 0.13.5
|
2
8
|
* Fix DeliveryReport `create_result#error` being nil despite an error being associated with it
|
3
9
|
|
data/docker-compose.yml
CHANGED
@@ -1,24 +1,25 @@
|
|
1
|
-
---
|
2
|
-
|
3
1
|
version: '2'
|
4
2
|
|
5
3
|
services:
|
6
|
-
zookeeper:
|
7
|
-
image: confluentinc/cp-zookeeper:5.2.6
|
8
|
-
environment:
|
9
|
-
ZOOKEEPER_CLIENT_PORT: 2181
|
10
|
-
ZOOKEEPER_TICK_TIME: 2000
|
11
|
-
|
12
4
|
kafka:
|
13
|
-
|
14
|
-
|
15
|
-
|
5
|
+
container_name: kafka
|
6
|
+
image: confluentinc/cp-kafka:7.5.0
|
7
|
+
|
16
8
|
ports:
|
17
9
|
- 9092:9092
|
10
|
+
|
18
11
|
environment:
|
19
|
-
|
20
|
-
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
21
|
-
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:29092,PLAINTEXT_HOST://localhost:9092
|
22
|
-
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
|
12
|
+
CLUSTER_ID: kafka-docker-cluster-1
|
23
13
|
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
|
24
14
|
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
15
|
+
KAFKA_PROCESS_ROLES: broker,controller
|
16
|
+
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
|
17
|
+
KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
|
18
|
+
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
|
19
|
+
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://127.0.0.1:9092
|
20
|
+
KAFKA_BROKER_ID: 1
|
21
|
+
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@127.0.0.1:9093
|
22
|
+
ALLOW_PLAINTEXT_LISTENER: 'yes'
|
23
|
+
KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true'
|
24
|
+
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
|
25
|
+
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
|
@@ -37,12 +37,13 @@ module Rdkafka
|
|
37
37
|
#
|
38
38
|
# @param max_wait_timeout [Numeric, nil] Amount of time to wait before timing out. If this is nil it does not time out.
|
39
39
|
# @param wait_timeout [Numeric] Amount of time we should wait before we recheck if the operation has completed
|
40
|
+
# @param raise_response_error [Boolean] should we raise error when waiting finishes
|
40
41
|
#
|
41
42
|
# @raise [RdkafkaError] When the operation failed
|
42
43
|
# @raise [WaitTimeoutError] When the timeout has been reached and the handle is still pending
|
43
44
|
#
|
44
45
|
# @return [Object] Operation-specific result
|
45
|
-
def wait(max_wait_timeout: 60, wait_timeout: 0.1)
|
46
|
+
def wait(max_wait_timeout: 60, wait_timeout: 0.1, raise_response_error: true)
|
46
47
|
timeout = if max_wait_timeout
|
47
48
|
CURRENT_TIME.call + max_wait_timeout
|
48
49
|
else
|
@@ -54,7 +55,7 @@ module Rdkafka
|
|
54
55
|
raise WaitTimeoutError.new("Waiting for #{operation_name} timed out after #{max_wait_timeout} seconds")
|
55
56
|
end
|
56
57
|
sleep wait_timeout
|
57
|
-
elsif self[:response] != 0
|
58
|
+
elsif self[:response] != 0 && raise_response_error
|
58
59
|
raise_error
|
59
60
|
else
|
60
61
|
return create_result
|
@@ -74,7 +75,7 @@ module Rdkafka
|
|
74
75
|
|
75
76
|
# Allow subclasses to override
|
76
77
|
def raise_error
|
77
|
-
|
78
|
+
RdkafkaError.validate!(self[:response])
|
78
79
|
end
|
79
80
|
|
80
81
|
# Error that is raised when waiting for the handle to complete
|
@@ -17,9 +17,12 @@ module Rdkafka
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def raise_error
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
RdkafkaError.validate!(
|
21
|
+
self[:response],
|
22
|
+
broker_message: CreatePartitionsReport.new(
|
23
|
+
self[:error_string],
|
24
|
+
self[:result_name]
|
25
|
+
).error_string
|
23
26
|
)
|
24
27
|
end
|
25
28
|
end
|
@@ -19,9 +19,12 @@ module Rdkafka
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def raise_error
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
RdkafkaError.validate!(
|
23
|
+
self[:response],
|
24
|
+
broker_message: CreateTopicReport.new(
|
25
|
+
self[:error_string],
|
26
|
+
self[:result_name]
|
27
|
+
).error_string
|
25
28
|
)
|
26
29
|
end
|
27
30
|
end
|
@@ -19,9 +19,12 @@ module Rdkafka
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def raise_error
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
RdkafkaError.validate!(
|
23
|
+
self[:response],
|
24
|
+
broker_message: DeleteTopicReport.new(
|
25
|
+
self[:error_string],
|
26
|
+
self[:result_name]
|
27
|
+
).error_string
|
25
28
|
)
|
26
29
|
end
|
27
30
|
end
|
data/lib/rdkafka/bindings.rb
CHANGED
@@ -6,6 +6,15 @@ require "logger"
|
|
6
6
|
|
7
7
|
module Rdkafka
|
8
8
|
# @private
|
9
|
+
#
|
10
|
+
# @note
|
11
|
+
# There are two types of responses related to errors:
|
12
|
+
# - rd_kafka_error_t - a C object that we need to remap into an error or null when no error
|
13
|
+
# - rd_kafka_resp_err_t - response error code (numeric) that we can use directly
|
14
|
+
#
|
15
|
+
# It is critical to ensure, that we handle them correctly. The result type should be:
|
16
|
+
# - rd_kafka_error_t - :pointer
|
17
|
+
# - rd_kafka_resp_err_t - :int
|
9
18
|
module Bindings
|
10
19
|
extend FFI::Library
|
11
20
|
|
@@ -35,7 +44,7 @@ module Rdkafka
|
|
35
44
|
|
36
45
|
# Polling
|
37
46
|
|
38
|
-
attach_function :rd_kafka_flush, [:pointer, :int], :
|
47
|
+
attach_function :rd_kafka_flush, [:pointer, :int], :int, blocking: true
|
39
48
|
attach_function :rd_kafka_poll, [:pointer, :int], :void, blocking: true
|
40
49
|
attach_function :rd_kafka_outq_len, [:pointer], :int, blocking: true
|
41
50
|
|
@@ -96,6 +105,11 @@ module Rdkafka
|
|
96
105
|
|
97
106
|
attach_function :rd_kafka_err2name, [:int], :string
|
98
107
|
attach_function :rd_kafka_err2str, [:int], :string
|
108
|
+
attach_function :rd_kafka_error_is_fatal, [:pointer], :int
|
109
|
+
attach_function :rd_kafka_error_is_retriable, [:pointer], :int
|
110
|
+
attach_function :rd_kafka_error_txn_requires_abort, [:pointer], :int
|
111
|
+
attach_function :rd_kafka_error_destroy, [:pointer], :void
|
112
|
+
attach_function :rd_kafka_error_code, [:pointer], :int
|
99
113
|
|
100
114
|
# Configuration
|
101
115
|
|
@@ -157,7 +171,7 @@ module Rdkafka
|
|
157
171
|
:void, [:pointer, :int, :string, :pointer]
|
158
172
|
) do |_client_prr, err_code, reason, _opaque|
|
159
173
|
if Rdkafka::Config.error_callback
|
160
|
-
error = Rdkafka::RdkafkaError.
|
174
|
+
error = Rdkafka::RdkafkaError.build(err_code, broker_message: reason)
|
161
175
|
error.set_backtrace(caller)
|
162
176
|
Rdkafka::Config.error_callback.call(error)
|
163
177
|
end
|
@@ -255,12 +269,19 @@ module Rdkafka
|
|
255
269
|
RD_KAFKA_VTYPE_TIMESTAMP = 8
|
256
270
|
RD_KAFKA_VTYPE_HEADER = 9
|
257
271
|
RD_KAFKA_VTYPE_HEADERS = 10
|
272
|
+
RD_KAFKA_PURGE_F_QUEUE = 1
|
273
|
+
RD_KAFKA_PURGE_F_INFLIGHT = 2
|
258
274
|
|
259
275
|
RD_KAFKA_MSG_F_COPY = 0x2
|
260
276
|
|
261
277
|
attach_function :rd_kafka_producev, [:pointer, :varargs], :int, blocking: true
|
278
|
+
attach_function :rd_kafka_purge, [:pointer, :int], :int, blocking: true
|
262
279
|
callback :delivery_cb, [:pointer, :pointer, :pointer], :void
|
263
280
|
attach_function :rd_kafka_conf_set_dr_msg_cb, [:pointer, :delivery_cb], :void
|
281
|
+
attach_function :rd_kafka_init_transactions, [:pointer, :int], :pointer, blocking: true
|
282
|
+
attach_function :rd_kafka_begin_transaction, [:pointer], :pointer, blocking: true
|
283
|
+
attach_function :rd_kafka_abort_transaction, [:pointer, :int], :pointer, blocking: true
|
284
|
+
attach_function :rd_kafka_commit_transaction, [:pointer, :int], :pointer, blocking: true
|
264
285
|
|
265
286
|
# Partitioner
|
266
287
|
PARTITIONERS = %w(random consistent consistent_random murmur2 murmur2_random fnv1a fnv1a_random).each_with_object({}) do |name, hsh|
|
data/lib/rdkafka/consumer.rb
CHANGED
@@ -68,9 +68,8 @@ module Rdkafka
|
|
68
68
|
response = @native_kafka.with_inner do |inner|
|
69
69
|
Rdkafka::Bindings.rd_kafka_subscribe(inner, tpl)
|
70
70
|
end
|
71
|
-
|
72
|
-
|
73
|
-
end
|
71
|
+
|
72
|
+
Rdkafka::RdkafkaError.validate!(response, "Error subscribing to '#{topics.join(', ')}'")
|
74
73
|
ensure
|
75
74
|
Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl) unless tpl.nil?
|
76
75
|
end
|
@@ -86,9 +85,10 @@ module Rdkafka
|
|
86
85
|
response = @native_kafka.with_inner do |inner|
|
87
86
|
Rdkafka::Bindings.rd_kafka_unsubscribe(inner)
|
88
87
|
end
|
89
|
-
|
90
|
-
|
91
|
-
|
88
|
+
|
89
|
+
Rdkafka::RdkafkaError.validate!(response)
|
90
|
+
|
91
|
+
nil
|
92
92
|
end
|
93
93
|
|
94
94
|
# Pause producing or consumption for the provided list of partitions
|
@@ -141,9 +141,10 @@ module Rdkafka
|
|
141
141
|
response = @native_kafka.with_inner do |inner|
|
142
142
|
Rdkafka::Bindings.rd_kafka_resume_partitions(inner, tpl)
|
143
143
|
end
|
144
|
-
|
145
|
-
|
146
|
-
|
144
|
+
|
145
|
+
Rdkafka::RdkafkaError.validate!(response, "Error resume '#{list.to_h}'")
|
146
|
+
|
147
|
+
nil
|
147
148
|
ensure
|
148
149
|
Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl)
|
149
150
|
end
|
@@ -162,9 +163,7 @@ module Rdkafka
|
|
162
163
|
Rdkafka::Bindings.rd_kafka_subscription(inner, ptr)
|
163
164
|
end
|
164
165
|
|
165
|
-
|
166
|
-
raise Rdkafka::RdkafkaError.new(response)
|
167
|
-
end
|
166
|
+
Rdkafka::RdkafkaError.validate!(response)
|
168
167
|
|
169
168
|
native = ptr.read_pointer
|
170
169
|
|
@@ -193,9 +192,8 @@ module Rdkafka
|
|
193
192
|
response = @native_kafka.with_inner do |inner|
|
194
193
|
Rdkafka::Bindings.rd_kafka_assign(inner, tpl)
|
195
194
|
end
|
196
|
-
|
197
|
-
|
198
|
-
end
|
195
|
+
|
196
|
+
Rdkafka::RdkafkaError.validate!(response, "Error assigning '#{list.to_h}'")
|
199
197
|
ensure
|
200
198
|
Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl)
|
201
199
|
end
|
@@ -213,9 +211,8 @@ module Rdkafka
|
|
213
211
|
response = @native_kafka.with_inner do |inner|
|
214
212
|
Rdkafka::Bindings.rd_kafka_assignment(inner, ptr)
|
215
213
|
end
|
216
|
-
|
217
|
-
|
218
|
-
end
|
214
|
+
|
215
|
+
Rdkafka::RdkafkaError.validate!(response)
|
219
216
|
|
220
217
|
tpl = ptr.read_pointer
|
221
218
|
|
@@ -263,9 +260,9 @@ module Rdkafka
|
|
263
260
|
response = @native_kafka.with_inner do |inner|
|
264
261
|
Rdkafka::Bindings.rd_kafka_committed(inner, tpl, timeout_ms)
|
265
262
|
end
|
266
|
-
|
267
|
-
|
268
|
-
|
263
|
+
|
264
|
+
Rdkafka::RdkafkaError.validate!(response)
|
265
|
+
|
269
266
|
TopicPartitionList.from_native_tpl(tpl)
|
270
267
|
ensure
|
271
268
|
Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl)
|
@@ -297,9 +294,8 @@ module Rdkafka
|
|
297
294
|
timeout_ms,
|
298
295
|
)
|
299
296
|
end
|
300
|
-
|
301
|
-
|
302
|
-
end
|
297
|
+
|
298
|
+
Rdkafka::RdkafkaError.validate!(response, "Error querying watermark offsets for partition #{partition} of #{topic}")
|
303
299
|
|
304
300
|
return low.read_array_of_int64(1).first, high.read_array_of_int64(1).first
|
305
301
|
ensure
|
@@ -387,9 +383,10 @@ module Rdkafka
|
|
387
383
|
message.partition,
|
388
384
|
message.offset
|
389
385
|
)
|
390
|
-
|
391
|
-
|
392
|
-
|
386
|
+
|
387
|
+
Rdkafka::RdkafkaError.validate!(response)
|
388
|
+
|
389
|
+
nil
|
393
390
|
ensure
|
394
391
|
if native_topic && !native_topic.null?
|
395
392
|
Rdkafka::Bindings.rd_kafka_topic_destroy(native_topic)
|
@@ -422,9 +419,9 @@ module Rdkafka
|
|
422
419
|
message.offset,
|
423
420
|
0 # timeout
|
424
421
|
)
|
425
|
-
|
426
|
-
|
427
|
-
|
422
|
+
Rdkafka::RdkafkaError.validate!(response)
|
423
|
+
|
424
|
+
nil
|
428
425
|
ensure
|
429
426
|
if native_topic && !native_topic.null?
|
430
427
|
Rdkafka::Bindings.rd_kafka_topic_destroy(native_topic)
|
@@ -455,9 +452,7 @@ module Rdkafka
|
|
455
452
|
)
|
456
453
|
end
|
457
454
|
|
458
|
-
|
459
|
-
raise Rdkafka::RdkafkaError.new(response)
|
460
|
-
end
|
455
|
+
Rdkafka::RdkafkaError.validate!(response)
|
461
456
|
|
462
457
|
TopicPartitionList.from_native_tpl(tpl)
|
463
458
|
ensure
|
@@ -492,9 +487,10 @@ module Rdkafka
|
|
492
487
|
response = @native_kafka.with_inner do |inner|
|
493
488
|
Rdkafka::Bindings.rd_kafka_commit(inner, tpl, async)
|
494
489
|
end
|
495
|
-
|
496
|
-
|
497
|
-
|
490
|
+
|
491
|
+
Rdkafka::RdkafkaError.validate!(response)
|
492
|
+
|
493
|
+
nil
|
498
494
|
ensure
|
499
495
|
Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(tpl) if tpl
|
500
496
|
end
|
@@ -519,9 +515,9 @@ module Rdkafka
|
|
519
515
|
# Create struct wrapper
|
520
516
|
native_message = Rdkafka::Bindings::Message.new(message_ptr)
|
521
517
|
# Raise error if needed
|
522
|
-
|
523
|
-
|
524
|
-
|
518
|
+
|
519
|
+
Rdkafka::RdkafkaError.validate!(native_message[:err])
|
520
|
+
|
525
521
|
# Create a message to pass out
|
526
522
|
Rdkafka::Consumer::Message.new(native_message)
|
527
523
|
end
|
data/lib/rdkafka/error.rb
CHANGED
@@ -18,12 +18,59 @@ module Rdkafka
|
|
18
18
|
# @return [String]
|
19
19
|
attr_reader :broker_message
|
20
20
|
|
21
|
+
class << self
|
22
|
+
def build_from_c(response_ptr, message_prefix = nil, broker_message: nil)
|
23
|
+
code = Rdkafka::Bindings.rd_kafka_error_code(response_ptr)
|
24
|
+
|
25
|
+
return false if code.zero?
|
26
|
+
|
27
|
+
message = broker_message || Rdkafka::Bindings.rd_kafka_err2str(code)
|
28
|
+
fatal = !Rdkafka::Bindings.rd_kafka_error_is_fatal(response_ptr).zero?
|
29
|
+
retryable = !Rdkafka::Bindings.rd_kafka_error_is_retriable(response_ptr).zero?
|
30
|
+
abortable = !Rdkafka::Bindings.rd_kafka_error_txn_requires_abort(response_ptr).zero?
|
31
|
+
|
32
|
+
Rdkafka::Bindings.rd_kafka_error_destroy(response_ptr)
|
33
|
+
|
34
|
+
new(
|
35
|
+
code,
|
36
|
+
message_prefix,
|
37
|
+
broker_message: message,
|
38
|
+
fatal: fatal,
|
39
|
+
retryable: retryable,
|
40
|
+
abortable: abortable
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def build(response_ptr_or_code, message_prefix = nil, broker_message: nil)
|
45
|
+
if response_ptr_or_code.is_a?(Integer)
|
46
|
+
response_ptr_or_code.zero? ? false : new(response_ptr_or_code, message_prefix, broker_message: broker_message)
|
47
|
+
else
|
48
|
+
build_from_c(response_ptr_or_code, message_prefix)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate!(response_ptr_or_code, message_prefix = nil, broker_message: nil)
|
53
|
+
error = build(response_ptr_or_code, message_prefix, broker_message: broker_message)
|
54
|
+
error ? raise(error) : false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
21
58
|
# @private
|
22
|
-
def initialize(
|
59
|
+
def initialize(
|
60
|
+
response,
|
61
|
+
message_prefix=nil,
|
62
|
+
broker_message: nil,
|
63
|
+
fatal: false,
|
64
|
+
retryable: false,
|
65
|
+
abortable: false
|
66
|
+
)
|
23
67
|
raise TypeError.new("Response has to be an integer") unless response.is_a? Integer
|
24
68
|
@rdkafka_response = response
|
25
69
|
@message_prefix = message_prefix
|
26
70
|
@broker_message = broker_message
|
71
|
+
@fatal = fatal
|
72
|
+
@retryable = retryable
|
73
|
+
@abortable = abortable
|
27
74
|
end
|
28
75
|
|
29
76
|
# This error's code, for example `:partition_eof`, `:msg_size_too_large`.
|
@@ -58,6 +105,18 @@ module Rdkafka
|
|
58
105
|
def ==(another_error)
|
59
106
|
another_error.is_a?(self.class) && (self.to_s == another_error.to_s)
|
60
107
|
end
|
108
|
+
|
109
|
+
def fatal?
|
110
|
+
@fatal
|
111
|
+
end
|
112
|
+
|
113
|
+
def retryable?
|
114
|
+
@retryable
|
115
|
+
end
|
116
|
+
|
117
|
+
def abortable?
|
118
|
+
@abortable
|
119
|
+
end
|
61
120
|
end
|
62
121
|
|
63
122
|
# Error with topic partition list returned by the underlying rdkafka library.
|
data/lib/rdkafka/metadata.rb
CHANGED
@@ -29,8 +29,7 @@ module Rdkafka
|
|
29
29
|
# Retrieve the Metadata
|
30
30
|
result = Rdkafka::Bindings.rd_kafka_metadata(native_client, topic_flag, native_topic, ptr, timeout_ms)
|
31
31
|
|
32
|
-
|
33
|
-
raise Rdkafka::RdkafkaError.new(result) unless result.zero?
|
32
|
+
Rdkafka::RdkafkaError.validate!(result)
|
34
33
|
|
35
34
|
metadata_from_native(ptr.read_pointer)
|
36
35
|
rescue ::Rdkafka::RdkafkaError => e
|
@@ -58,11 +57,12 @@ module Rdkafka
|
|
58
57
|
|
59
58
|
@topics = Array.new(metadata[:topics_count]) do |i|
|
60
59
|
topic = TopicMetadata.new(metadata[:topics_metadata] + (i * TopicMetadata.size))
|
61
|
-
|
60
|
+
|
61
|
+
RdkafkaError.validate!(topic[:rd_kafka_resp_err])
|
62
62
|
|
63
63
|
partitions = Array.new(topic[:partition_count]) do |j|
|
64
64
|
partition = PartitionMetadata.new(topic[:partitions_metadata] + (j * PartitionMetadata.size))
|
65
|
-
|
65
|
+
RdkafkaError.validate!(partition[:rd_kafka_resp_err])
|
66
66
|
partition.to_h
|
67
67
|
end
|
68
68
|
topic.to_h.merge!(partitions: partitions)
|
@@ -31,7 +31,7 @@ module Rdkafka
|
|
31
31
|
# For part of errors, we will not get a topic name reference and in cases like this
|
32
32
|
# we should not return it
|
33
33
|
self[:topic_name].null? ? nil : self[:topic_name].read_string,
|
34
|
-
RdkafkaError.
|
34
|
+
Rdkafka::RdkafkaError.build(self[:response])
|
35
35
|
)
|
36
36
|
end
|
37
37
|
end
|
data/lib/rdkafka/producer.rb
CHANGED
@@ -63,6 +63,47 @@ module Rdkafka
|
|
63
63
|
@delivery_callback_arity = arity(callback)
|
64
64
|
end
|
65
65
|
|
66
|
+
# Init transactions
|
67
|
+
# Run once per producer
|
68
|
+
def init_transactions
|
69
|
+
closed_producer_check(__method__)
|
70
|
+
|
71
|
+
@native_kafka.with_inner do |inner|
|
72
|
+
response_ptr = Rdkafka::Bindings.rd_kafka_init_transactions(inner, -1)
|
73
|
+
|
74
|
+
Rdkafka::RdkafkaError.validate!(response_ptr) || true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def begin_transaction
|
79
|
+
closed_producer_check(__method__)
|
80
|
+
|
81
|
+
@native_kafka.with_inner do |inner|
|
82
|
+
response_ptr = Rdkafka::Bindings.rd_kafka_begin_transaction(inner)
|
83
|
+
|
84
|
+
Rdkafka::RdkafkaError.validate!(response_ptr)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def commit_transaction(timeout_ms = -1)
|
89
|
+
closed_producer_check(__method__)
|
90
|
+
|
91
|
+
@native_kafka.with_inner do |inner|
|
92
|
+
response_ptr = Rdkafka::Bindings.rd_kafka_commit_transaction(inner, timeout_ms)
|
93
|
+
|
94
|
+
Rdkafka::RdkafkaError.validate!(response_ptr)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def abort_transaction(timeout_ms = -1)
|
99
|
+
closed_producer_check(__method__)
|
100
|
+
|
101
|
+
@native_kafka.with_inner do |inner|
|
102
|
+
response_ptr = Rdkafka::Bindings.rd_kafka_abort_transaction(inner, timeout_ms)
|
103
|
+
Rdkafka::RdkafkaError.validate!(response_ptr)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
66
107
|
# Close this producer and wait for the internal poll queue to empty.
|
67
108
|
def close
|
68
109
|
return if closed?
|
@@ -79,12 +120,50 @@ module Rdkafka
|
|
79
120
|
# in seconds. Call this before closing a producer to ensure delivery of all messages.
|
80
121
|
#
|
81
122
|
# @param timeout_ms [Integer] how long should we wait for flush of all messages
|
123
|
+
# @return [Boolean] true if no more data and all was flushed, false in case there are still
|
124
|
+
# outgoing messages after the timeout
|
125
|
+
#
|
126
|
+
# @note We raise an exception for other errors because based on the librdkafka docs, there
|
127
|
+
# should be no other errors.
|
128
|
+
#
|
129
|
+
# @note For `timed_out` we do not raise an error to keep it backwards compatible
|
82
130
|
def flush(timeout_ms=5_000)
|
83
131
|
closed_producer_check(__method__)
|
84
132
|
|
133
|
+
error = @native_kafka.with_inner do |inner|
|
134
|
+
response = Rdkafka::Bindings.rd_kafka_flush(inner, timeout_ms)
|
135
|
+
Rdkafka::RdkafkaError.build(response)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Early skip not to build the error message
|
139
|
+
return true unless error
|
140
|
+
return false if error.code == :timed_out
|
141
|
+
|
142
|
+
raise(error)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Purges the outgoing queue and releases all resources.
|
146
|
+
#
|
147
|
+
# Useful when closing the producer with outgoing messages to unstable clusters or when for
|
148
|
+
# any other reasons waiting cannot go on anymore. This purges both the queue and all the
|
149
|
+
# inflight requests + updates the delivery handles statuses so they can be materialized into
|
150
|
+
# `purge_queue` errors.
|
151
|
+
def purge
|
152
|
+
closed_producer_check(__method__)
|
153
|
+
|
85
154
|
@native_kafka.with_inner do |inner|
|
86
|
-
|
155
|
+
response = Bindings.rd_kafka_purge(
|
156
|
+
inner,
|
157
|
+
Bindings::RD_KAFKA_PURGE_F_QUEUE | Bindings::RD_KAFKA_PURGE_F_INFLIGHT
|
158
|
+
)
|
159
|
+
|
160
|
+
Rdkafka::RdkafkaError.validate!(response)
|
87
161
|
end
|
162
|
+
|
163
|
+
# Wait for the purge to affect everything
|
164
|
+
sleep(0.001) until flush(100)
|
165
|
+
|
166
|
+
true
|
88
167
|
end
|
89
168
|
|
90
169
|
# Partition count for a given topic.
|
@@ -208,7 +287,7 @@ module Rdkafka
|
|
208
287
|
# Raise error if the produce call was not successful
|
209
288
|
if response != 0
|
210
289
|
DeliveryHandle.remove(delivery_handle.to_ptr.address)
|
211
|
-
|
290
|
+
Rdkafka::RdkafkaError.validate!(response)
|
212
291
|
end
|
213
292
|
|
214
293
|
delivery_handle
|
data/lib/rdkafka/version.rb
CHANGED
@@ -45,10 +45,12 @@ describe Rdkafka::Admin::CreateTopicHandle do
|
|
45
45
|
describe "#raise_error" do
|
46
46
|
let(:pending_handle) { false }
|
47
47
|
|
48
|
-
|
48
|
+
before { subject[:response] = -1 }
|
49
|
+
|
50
|
+
it "should raise the appropriate error when there is an error" do
|
49
51
|
expect {
|
50
52
|
subject.raise_error
|
51
|
-
}.to raise_exception(Rdkafka::RdkafkaError, /
|
53
|
+
}.to raise_exception(Rdkafka::RdkafkaError, /Unknown broker error \(unknown\)/)
|
52
54
|
end
|
53
55
|
end
|
54
56
|
end
|
@@ -45,10 +45,12 @@ describe Rdkafka::Admin::DeleteTopicHandle do
|
|
45
45
|
describe "#raise_error" do
|
46
46
|
let(:pending_handle) { false }
|
47
47
|
|
48
|
+
before { subject[:response] = -1 }
|
49
|
+
|
48
50
|
it "should raise the appropriate error" do
|
49
51
|
expect {
|
50
52
|
subject.raise_error
|
51
|
-
}.to raise_exception(Rdkafka::RdkafkaError, /
|
53
|
+
}.to raise_exception(Rdkafka::RdkafkaError, /Unknown broker error \(unknown\)/)
|
52
54
|
end
|
53
55
|
end
|
54
56
|
end
|
data/spec/rdkafka/admin_spec.rb
CHANGED
@@ -33,7 +33,7 @@ describe Rdkafka::Admin do
|
|
33
33
|
}.to raise_exception { |ex|
|
34
34
|
expect(ex).to be_a(Rdkafka::RdkafkaError)
|
35
35
|
expect(ex.message).to match(/Broker: Invalid topic \(topic_exception\)/)
|
36
|
-
expect(ex.broker_message).to match(/Topic name.*is
|
36
|
+
expect(ex.broker_message).to match(/Topic name.*is invalid: .* contains one or more characters other than ASCII alphanumerics, '.', '_' and '-'/)
|
37
37
|
}
|
38
38
|
end
|
39
39
|
end
|
@@ -31,7 +31,7 @@ describe Rdkafka::Metadata do
|
|
31
31
|
it "#brokers returns our single broker" do
|
32
32
|
expect(subject.brokers.length).to eq(1)
|
33
33
|
expect(subject.brokers[0][:broker_id]).to eq(1)
|
34
|
-
expect(subject.brokers[0][:broker_name]).to eq("
|
34
|
+
expect(subject.brokers[0][:broker_name]).to eq("127.0.0.1")
|
35
35
|
expect(subject.brokers[0][:broker_port]).to eq(9092)
|
36
36
|
end
|
37
37
|
|
@@ -54,7 +54,7 @@ describe Rdkafka::Metadata do
|
|
54
54
|
it "#brokers returns our single broker" do
|
55
55
|
expect(subject.brokers.length).to eq(1)
|
56
56
|
expect(subject.brokers[0][:broker_id]).to eq(1)
|
57
|
-
expect(subject.brokers[0][:broker_name]).to eq("
|
57
|
+
expect(subject.brokers[0][:broker_name]).to eq("127.0.0.1")
|
58
58
|
expect(subject.brokers[0][:broker_port]).to eq(9092)
|
59
59
|
end
|
60
60
|
|
@@ -559,6 +559,22 @@ describe Rdkafka::Producer do
|
|
559
559
|
end
|
560
560
|
end
|
561
561
|
|
562
|
+
context "when not being able to deliver the message" do
|
563
|
+
let(:producer) do
|
564
|
+
rdkafka_producer_config(
|
565
|
+
"bootstrap.servers": "localhost:9093",
|
566
|
+
"message.timeout.ms": 100
|
567
|
+
).producer
|
568
|
+
end
|
569
|
+
|
570
|
+
it "should contain the error in the response when not deliverable" do
|
571
|
+
handler = producer.produce(topic: 'produce_test_topic', payload: nil)
|
572
|
+
# Wait for the async callbacks and delivery registry to update
|
573
|
+
sleep(2)
|
574
|
+
expect(handler.create_result.error).to be_a(Rdkafka::RdkafkaError)
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
562
578
|
describe '#partition_count' do
|
563
579
|
it { expect(producer.partition_count('example_topic')).to eq(1) }
|
564
580
|
|
@@ -628,19 +644,274 @@ describe Rdkafka::Producer do
|
|
628
644
|
end
|
629
645
|
end
|
630
646
|
|
631
|
-
|
647
|
+
describe '#flush' do
|
648
|
+
it "should return flush when it can flush all outstanding messages or when no messages" do
|
649
|
+
producer.produce(
|
650
|
+
topic: "produce_test_topic",
|
651
|
+
payload: "payload headers",
|
652
|
+
key: "key headers",
|
653
|
+
headers: {}
|
654
|
+
)
|
655
|
+
|
656
|
+
expect(producer.flush(5_000)).to eq(true)
|
657
|
+
end
|
658
|
+
|
659
|
+
context 'when it cannot flush due to a timeout' do
|
660
|
+
let(:producer) do
|
661
|
+
rdkafka_producer_config(
|
662
|
+
"bootstrap.servers": "localhost:9093",
|
663
|
+
"message.timeout.ms": 2_000
|
664
|
+
).producer
|
665
|
+
end
|
666
|
+
|
667
|
+
after do
|
668
|
+
# Allow rdkafka to evict message preventing memory-leak
|
669
|
+
sleep(2)
|
670
|
+
end
|
671
|
+
|
672
|
+
it "should return false on flush when cannot deliver and beyond timeout" do
|
673
|
+
producer.produce(
|
674
|
+
topic: "produce_test_topic",
|
675
|
+
payload: "payload headers",
|
676
|
+
key: "key headers",
|
677
|
+
headers: {}
|
678
|
+
)
|
679
|
+
|
680
|
+
expect(producer.flush(1_000)).to eq(false)
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
684
|
+
context 'when there is a different error' do
|
685
|
+
before { allow(Rdkafka::Bindings).to receive(:rd_kafka_flush).and_return(-199) }
|
686
|
+
|
687
|
+
it 'should raise it' do
|
688
|
+
expect { producer.flush }.to raise_error(Rdkafka::RdkafkaError)
|
689
|
+
end
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
describe '#purge' do
|
694
|
+
context 'when no outgoing messages' do
|
695
|
+
it { expect(producer.purge).to eq(true) }
|
696
|
+
end
|
697
|
+
|
698
|
+
context 'when librdkafka purge returns an error' do
|
699
|
+
before { expect(Rdkafka::Bindings).to receive(:rd_kafka_purge).and_return(-153) }
|
700
|
+
|
701
|
+
it 'expect to raise an error' do
|
702
|
+
expect { producer.purge }.to raise_error(Rdkafka::RdkafkaError, /retry/)
|
703
|
+
end
|
704
|
+
end
|
705
|
+
|
706
|
+
context 'when there are outgoing things in the queue' do
|
707
|
+
let(:producer) do
|
708
|
+
rdkafka_producer_config(
|
709
|
+
"bootstrap.servers": "localhost:9093",
|
710
|
+
"message.timeout.ms": 2_000
|
711
|
+
).producer
|
712
|
+
end
|
713
|
+
|
714
|
+
it "should should purge and move forward" do
|
715
|
+
producer.produce(
|
716
|
+
topic: "produce_test_topic",
|
717
|
+
payload: "payload headers"
|
718
|
+
)
|
719
|
+
|
720
|
+
expect(producer.purge).to eq(true)
|
721
|
+
expect(producer.flush(1_000)).to eq(true)
|
722
|
+
end
|
723
|
+
|
724
|
+
it "should materialize the delivery handles" do
|
725
|
+
handle = producer.produce(
|
726
|
+
topic: "produce_test_topic",
|
727
|
+
payload: "payload headers"
|
728
|
+
)
|
729
|
+
|
730
|
+
expect(producer.purge).to eq(true)
|
731
|
+
|
732
|
+
expect { handle.wait }.to raise_error(Rdkafka::RdkafkaError, /purge_queue/)
|
733
|
+
end
|
734
|
+
|
735
|
+
context "when using delivery_callback" do
|
736
|
+
let(:delivery_reports) { [] }
|
737
|
+
|
738
|
+
let(:delivery_callback) do
|
739
|
+
->(delivery_report) { delivery_reports << delivery_report }
|
740
|
+
end
|
741
|
+
|
742
|
+
before { producer.delivery_callback = delivery_callback }
|
743
|
+
|
744
|
+
it "should run the callback" do
|
745
|
+
handle = producer.produce(
|
746
|
+
topic: "produce_test_topic",
|
747
|
+
payload: "payload headers"
|
748
|
+
)
|
749
|
+
|
750
|
+
expect(producer.purge).to eq(true)
|
751
|
+
# queue purge
|
752
|
+
expect(delivery_reports[0].error).to eq(-152)
|
753
|
+
end
|
754
|
+
end
|
755
|
+
end
|
756
|
+
end
|
757
|
+
|
758
|
+
context 'when working with transactions' do
|
632
759
|
let(:producer) do
|
633
760
|
rdkafka_producer_config(
|
634
|
-
|
635
|
-
|
761
|
+
'transactional.id': SecureRandom.uuid,
|
762
|
+
'transaction.timeout.ms': 5_000
|
636
763
|
).producer
|
637
764
|
end
|
638
765
|
|
639
|
-
it
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
766
|
+
it 'expect not to allow to produce without transaction init' do
|
767
|
+
expect do
|
768
|
+
producer.produce(topic: 'produce_test_topic', payload: 'data')
|
769
|
+
end.to raise_error(Rdkafka::RdkafkaError, /Erroneous state \(state\)/)
|
770
|
+
end
|
771
|
+
|
772
|
+
it 'expect to raise error when transactions are initialized but producing not in one' do
|
773
|
+
producer.init_transactions
|
774
|
+
|
775
|
+
expect do
|
776
|
+
producer.produce(topic: 'produce_test_topic', payload: 'data')
|
777
|
+
end.to raise_error(Rdkafka::RdkafkaError, /Erroneous state \(state\)/)
|
778
|
+
end
|
779
|
+
|
780
|
+
it 'expect to allow to produce within a transaction, finalize and ship data' do
|
781
|
+
producer.init_transactions
|
782
|
+
producer.begin_transaction
|
783
|
+
handle1 = producer.produce(topic: 'produce_test_topic', payload: 'data1', partition: 1)
|
784
|
+
handle2 = producer.produce(topic: 'example_topic', payload: 'data2', partition: 0)
|
785
|
+
producer.commit_transaction
|
786
|
+
|
787
|
+
report1 = handle1.wait(max_wait_timeout: 15)
|
788
|
+
report2 = handle2.wait(max_wait_timeout: 15)
|
789
|
+
|
790
|
+
message1 = wait_for_message(
|
791
|
+
topic: "produce_test_topic",
|
792
|
+
delivery_report: report1,
|
793
|
+
consumer: consumer
|
794
|
+
)
|
795
|
+
|
796
|
+
expect(message1.partition).to eq 1
|
797
|
+
expect(message1.payload).to eq "data1"
|
798
|
+
expect(message1.timestamp).to be_within(10).of(Time.now)
|
799
|
+
|
800
|
+
message2 = wait_for_message(
|
801
|
+
topic: "example_topic",
|
802
|
+
delivery_report: report2,
|
803
|
+
consumer: consumer
|
804
|
+
)
|
805
|
+
|
806
|
+
expect(message2.partition).to eq 0
|
807
|
+
expect(message2.payload).to eq "data2"
|
808
|
+
expect(message2.timestamp).to be_within(10).of(Time.now)
|
809
|
+
end
|
810
|
+
|
811
|
+
it 'expect not to send data and propagate purge queue error on abort' do
|
812
|
+
producer.init_transactions
|
813
|
+
producer.begin_transaction
|
814
|
+
handle1 = producer.produce(topic: 'produce_test_topic', payload: 'data1', partition: 1)
|
815
|
+
handle2 = producer.produce(topic: 'example_topic', payload: 'data2', partition: 0)
|
816
|
+
producer.abort_transaction
|
817
|
+
|
818
|
+
expect { handle1.wait(max_wait_timeout: 15) }
|
819
|
+
.to raise_error(Rdkafka::RdkafkaError, /Purged in queue \(purge_queue\)/)
|
820
|
+
expect { handle2.wait(max_wait_timeout: 15) }
|
821
|
+
.to raise_error(Rdkafka::RdkafkaError, /Purged in queue \(purge_queue\)/)
|
822
|
+
end
|
823
|
+
|
824
|
+
it 'expect to have non retryable, non abortable and not fatal error on abort' do
|
825
|
+
producer.init_transactions
|
826
|
+
producer.begin_transaction
|
827
|
+
handle = producer.produce(topic: 'produce_test_topic', payload: 'data1', partition: 1)
|
828
|
+
producer.abort_transaction
|
829
|
+
|
830
|
+
response = handle.wait(raise_response_error: false)
|
831
|
+
|
832
|
+
expect(response.error).to be_a(Rdkafka::RdkafkaError)
|
833
|
+
expect(response.error.retryable?).to eq(false)
|
834
|
+
expect(response.error.fatal?).to eq(false)
|
835
|
+
expect(response.error.abortable?).to eq(false)
|
836
|
+
end
|
837
|
+
|
838
|
+
# This may not always crash, depends on load but no other way to check it
|
839
|
+
context 'when timeout is too short and error occurs and we can abort' do
|
840
|
+
let(:producer) do
|
841
|
+
rdkafka_producer_config(
|
842
|
+
'transactional.id': SecureRandom.uuid,
|
843
|
+
'transaction.timeout.ms': 1_000
|
844
|
+
).producer
|
845
|
+
end
|
846
|
+
|
847
|
+
it 'expect to allow to produce within a transaction, finalize and ship data' do
|
848
|
+
producer.init_transactions
|
849
|
+
producer.begin_transaction
|
850
|
+
|
851
|
+
handle1 = producer.produce(topic: 'produce_test_topic', payload: 'data1', partition: 1)
|
852
|
+
handle2 = producer.produce(topic: 'example_topic', payload: 'data2', partition: 0)
|
853
|
+
|
854
|
+
begin
|
855
|
+
producer.commit_transaction
|
856
|
+
rescue Rdkafka::RdkafkaError => e
|
857
|
+
next unless e.abortable?
|
858
|
+
|
859
|
+
producer.abort_transaction
|
860
|
+
|
861
|
+
expect { handle1.wait(max_wait_timeout: 15) }.to raise_error(Rdkafka::RdkafkaError)
|
862
|
+
expect { handle2.wait(max_wait_timeout: 15) }.to raise_error(Rdkafka::RdkafkaError)
|
863
|
+
end
|
864
|
+
end
|
865
|
+
end
|
866
|
+
|
867
|
+
context 'fencing against previous active producer with same transactional id' do
|
868
|
+
let(:transactional_id) { SecureRandom.uuid }
|
869
|
+
|
870
|
+
let(:producer1) do
|
871
|
+
rdkafka_producer_config(
|
872
|
+
'transactional.id': transactional_id,
|
873
|
+
'transaction.timeout.ms': 10_000
|
874
|
+
).producer
|
875
|
+
end
|
876
|
+
|
877
|
+
let(:producer2) do
|
878
|
+
rdkafka_producer_config(
|
879
|
+
'transactional.id': transactional_id,
|
880
|
+
'transaction.timeout.ms': 10_000
|
881
|
+
).producer
|
882
|
+
end
|
883
|
+
|
884
|
+
after do
|
885
|
+
producer1.close
|
886
|
+
producer2.close
|
887
|
+
end
|
888
|
+
|
889
|
+
it 'expect older producer not to be able to commit when fanced out' do
|
890
|
+
producer1.init_transactions
|
891
|
+
producer1.begin_transaction
|
892
|
+
producer1.produce(topic: 'produce_test_topic', payload: 'data1', partition: 1)
|
893
|
+
|
894
|
+
producer2.init_transactions
|
895
|
+
producer2.begin_transaction
|
896
|
+
producer2.produce(topic: 'produce_test_topic', payload: 'data1', partition: 1)
|
897
|
+
|
898
|
+
expect { producer1.commit_transaction }
|
899
|
+
.to raise_error(Rdkafka::RdkafkaError, /This instance has been fenced/)
|
900
|
+
|
901
|
+
error = false
|
902
|
+
|
903
|
+
begin
|
904
|
+
producer1.commit_transaction
|
905
|
+
rescue Rdkafka::RdkafkaError => e
|
906
|
+
error = e
|
907
|
+
end
|
908
|
+
|
909
|
+
expect(error.fatal?).to eq(true)
|
910
|
+
expect(error.abortable?).to eq(false)
|
911
|
+
expect(error.retryable?).to eq(false)
|
912
|
+
|
913
|
+
expect { producer2.commit_transaction }.not_to raise_error
|
914
|
+
end
|
644
915
|
end
|
645
916
|
end
|
646
917
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -11,6 +11,7 @@ require "pry"
|
|
11
11
|
require "rspec"
|
12
12
|
require "rdkafka"
|
13
13
|
require "timeout"
|
14
|
+
require 'securerandom'
|
14
15
|
|
15
16
|
def rdkafka_base_config
|
16
17
|
{
|
@@ -134,6 +135,7 @@ RSpec.configure do |config|
|
|
134
135
|
rake_test_topic: 3,
|
135
136
|
watermarks_test_topic: 3,
|
136
137
|
partitioner_test_topic: 25,
|
138
|
+
example_topic: 1
|
137
139
|
}.each do |topic, partitions|
|
138
140
|
create_topic_handle = admin.create_topic(topic.to_s, partitions, 1)
|
139
141
|
begin
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: karafka-rdkafka
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.13.
|
4
|
+
version: 0.13.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thijs Cadier
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
AnG1dJU+yL2BK7vaVytLTstJME5mepSZ46qqIJXMuWob/YPDmVaBF39TDSG9e34s
|
36
36
|
msG3BiCqgOgHAnL23+CN3Rt8MsuRfEtoTKpJVcCfoEoNHOkc
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date: 2023-
|
38
|
+
date: 2023-10-17 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: ffi
|
metadata.gz.sig
CHANGED
Binary file
|