karafka-rdkafka 0.30.0 → 0.30.2
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
- data/CHANGELOG.md +6 -0
- data/lib/rdkafka/bindings.rb +12 -0
- data/lib/rdkafka/defaults.rb +4 -0
- data/lib/rdkafka/share_consumer.rb +68 -8
- data/lib/rdkafka/version.rb +1 -1
- data/renovate.json +3 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7b68458fe1759b2645cce196bd5a602c236fe36853e428940641301272d5ecaf
|
|
4
|
+
data.tar.gz: 6248362a62a402e6004286abc9a7c1f7f29baa9ac375983fa1fc0a2d64159fc3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c69f725864465b708ac3159db0df5a8597e8cf6894f6108ed5ca34654bb4e905fc6dc1e79bd6689bbd2ff4378f4e7bb2f1edfb1bda4416e944912154fda608de
|
|
7
|
+
data.tar.gz: e0193cf81b907d704b97a27c1d0998f14b83ffb12361b8f0557e87b4345ae58c29caa2d2544192e8524a9feb719b3d707bb42b24cf2d4079ace9d481075277db
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Rdkafka Changelog
|
|
2
2
|
|
|
3
|
+
## 0.30.2 (2026-10-01)
|
|
4
|
+
- [Feature] Add `ShareConsumer#events_poll` (and `#events_poll_nb`) to service the statistics, error, log and OAuthBearer callbacks without acquiring records.
|
|
5
|
+
|
|
6
|
+
## 0.30.1 (2026-09-30)
|
|
7
|
+
- [Fix] Derive `ShareConsumer#name` from the native handle at creation (mirroring `Consumer#name`) instead of leaving it `nil` until an OAuthBearer callback happened to set it. A `nil` name meant downstreams that route the global statistics and error callbacks by client name (e.g. Karafka) silently dropped every share-consumer statistic and background error.
|
|
8
|
+
|
|
3
9
|
## 0.30.0 (2026-09-25)
|
|
4
10
|
- [Fix] Register share consumers in `Rdkafka::Clients` and destroy the native handle when `Config#share_consumer` fails part-way. Share consumers own the native handle directly rather than a `NativeKafka` wrapper, so they bypass `build_native_client` and were missed by the `at_exit` shutdown hook, leaving librdkafka to be unloaded with a live share handle.
|
|
5
11
|
- [Feature] Add preview support for KIP-932 share groups ("Queues for Kafka") via `Rdkafka::ShareConsumer`, created with `Config#share_consumer`. Members consume the same partitions cooperatively with per-record acknowledgements (`:accept`, `:release`, `:reject`) instead of committed offsets. Like the regular `Consumer`, `ShareConsumer` is a thin binding that exposes the librdkafka share primitives (`#subscribe`/`#unsubscribe`/`#subscription`, batch `#poll`, `#acknowledge`, `#commit_sync`, `#commit_async`, `#acknowledgement_commit_callback=`, `#close`) but drives none of them itself - there is no built-in poll loop, acknowledgement strategy or offset/lifecycle orchestration, leaving that to a higher layer such as Karafka. The handle is single-threaded by design, servicing log, statistics and error callbacks from within `#poll`. Requires a broker with share groups enabled (Apache Kafka 4.2.0+); librdkafka marks the feature as preview and not production-ready.
|
data/lib/rdkafka/bindings.rb
CHANGED
|
@@ -517,6 +517,18 @@ module Rdkafka
|
|
|
517
517
|
# rd_kafka_share_consumer_new instead of rd_kafka_new. It is single-threaded by design and
|
|
518
518
|
# librdkafka itself rejects concurrent access with RD_KAFKA_RESP_ERR__CONFLICT.
|
|
519
519
|
|
|
520
|
+
# Minimal mirror of the head of librdkafka's `struct rd_kafka_share_s`, whose first member is
|
|
521
|
+
# the wrapped `rd_kafka_t` (`rkshare_rk`). `rd_kafka_share_t` has no public accessors of its
|
|
522
|
+
# own - not even for the client name - but the handle it wraps is a normal `rd_kafka_t` and is
|
|
523
|
+
# the very handle librdkafka passes to the statistics, error and OAuthBearer callbacks. Reading
|
|
524
|
+
# it lets a share consumer report a name that matches its statistics `name` field, which
|
|
525
|
+
# `rd_kafka_name` cannot do when handed the `rd_kafka_share_t` directly (it would read an
|
|
526
|
+
# unrelated field and return garbage). This mirrors how the gem already maps other librdkafka
|
|
527
|
+
# structs by layout for the pinned librdkafka version.
|
|
528
|
+
class NativeShareConsumer < FFI::Struct
|
|
529
|
+
layout :rkshare_rk, :pointer
|
|
530
|
+
end
|
|
531
|
+
|
|
520
532
|
# Acknowledge types for share consumer records
|
|
521
533
|
RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_ACCEPT = 1
|
|
522
534
|
RD_KAFKA_SHARE_ACKNOWLEDGE_TYPE_RELEASE = 2
|
data/lib/rdkafka/defaults.rb
CHANGED
|
@@ -51,6 +51,10 @@ module Rdkafka
|
|
|
51
51
|
# @see ShareConsumer#poll
|
|
52
52
|
SHARE_CONSUMER_POLL_TIMEOUT_MS = 250
|
|
53
53
|
|
|
54
|
+
# Share consumer timeout for events_poll (0 = non-blocking async)
|
|
55
|
+
# @see ShareConsumer#events_poll
|
|
56
|
+
SHARE_CONSUMER_EVENTS_POLL_TIMEOUT_MS = 0
|
|
57
|
+
|
|
54
58
|
# Share consumer timeout for synchronous acknowledgement commits
|
|
55
59
|
# @see ShareConsumer#commit_sync
|
|
56
60
|
SHARE_CONSUMER_COMMIT_SYNC_TIMEOUT_MS = 5_000
|
|
@@ -22,9 +22,10 @@ module Rdkafka
|
|
|
22
22
|
# share consumer per thread. librdkafka enforces this and calls from a second thread raise
|
|
23
23
|
# an `RdkafkaError` with code `conflict`. This class is deliberately not built on
|
|
24
24
|
# {NativeKafka}: there is no background polling thread (statistics, error and log callbacks
|
|
25
|
-
# are all serviced from within {#poll}
|
|
26
|
-
#
|
|
27
|
-
#
|
|
25
|
+
# are all serviced from within {#poll}, or from {#events_poll} when the caller is not polling
|
|
26
|
+
# for records) and the native handle must never be used from a polling thread, so only the
|
|
27
|
+
# {#close} interaction is synchronized here. {#close} may be called from any thread and waits
|
|
28
|
+
# for in-flight calls to finish before tearing down.
|
|
28
29
|
#
|
|
29
30
|
# Preview limitations inherited from librdkafka worth knowing at this layer:
|
|
30
31
|
# - `max.poll.records` (default 500) is a soft bound
|
|
@@ -57,14 +58,17 @@ module Rdkafka
|
|
|
57
58
|
private_constant :State
|
|
58
59
|
|
|
59
60
|
# The client name librdkafka reports for this consumer (e.g. "rdkafka#consumer-1"), used to
|
|
60
|
-
# correlate global callbacks (
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
61
|
+
# correlate the global callbacks (statistics, error and the OAuthBearer token refresh
|
|
62
|
+
# callback, all of which carry the client name) with this instance. Captured from the native
|
|
63
|
+
# handle when the consumer is created, so it is available immediately - before the first
|
|
64
|
+
# {#poll} and without OAuth configured - and stays readable after the consumer is closed or
|
|
65
|
+
# inherited by a forked child.
|
|
66
|
+
# @return [String] the librdkafka client name
|
|
64
67
|
attr_reader :name
|
|
65
68
|
|
|
66
69
|
# @private
|
|
67
|
-
#
|
|
70
|
+
# Lets the OAuthBearer token refresh callback set the name from its callback context. It
|
|
71
|
+
# carries the same `rd_kafka_name` value already captured at creation, so both paths agree.
|
|
68
72
|
attr_writer :name
|
|
69
73
|
|
|
70
74
|
# @private
|
|
@@ -80,6 +84,18 @@ module Rdkafka
|
|
|
80
84
|
# every teardown path can skip the native destroy in a forked child (librdkafka is not
|
|
81
85
|
# fork-safe).
|
|
82
86
|
@state = State.new(native, nil, Process.pid)
|
|
87
|
+
# Capture the librdkafka client name up front. It is needed to correlate this consumer with
|
|
88
|
+
# the global statistics and error callbacks (which carry the client name) - the common
|
|
89
|
+
# downstream pattern (e.g. Karafka) filters those broadcast callbacks by matching `name`.
|
|
90
|
+
#
|
|
91
|
+
# `rd_kafka_name` has no `rd_kafka_share_t` overload: handing it the share handle reads an
|
|
92
|
+
# unrelated field and returns garbage. The share handle's first member is the `rd_kafka_t`
|
|
93
|
+
# it wraps (`rkshare_rk`), which is the same handle librdkafka passes to those callbacks, so
|
|
94
|
+
# the name read from it matches the statistics `name` field. Capturing it here (rather than
|
|
95
|
+
# lazily like the regular consumer) keeps it available before the first poll, without OAuth,
|
|
96
|
+
# and after close or in a forked child, where the native handle must not be touched.
|
|
97
|
+
# librdkafka assigns the name at creation, so no broker connection is required.
|
|
98
|
+
@name = Rdkafka::Bindings.rd_kafka_name(Rdkafka::Bindings::NativeShareConsumer.new(native)[:rkshare_rk])
|
|
83
99
|
# Guards handle teardown against in-flight calls: increments happen under this mutex and
|
|
84
100
|
# close holds it while draining and destroying (mirrors NativeKafka's approach)
|
|
85
101
|
@access_mutex = Mutex.new
|
|
@@ -249,6 +265,50 @@ module Rdkafka
|
|
|
249
265
|
end
|
|
250
266
|
end
|
|
251
267
|
|
|
268
|
+
# Polls the main rdkafka queue (not the share one), serving queued global callbacks without
|
|
269
|
+
# acquiring any records.
|
|
270
|
+
#
|
|
271
|
+
# Events will cause application-provided callbacks to be called:
|
|
272
|
+
# - error callbacks
|
|
273
|
+
# - stats callbacks
|
|
274
|
+
# - log and OAuthBearer token refresh callbacks
|
|
275
|
+
#
|
|
276
|
+
# Lets a caller keep those callbacks flowing while it is not polling for records (waiting on
|
|
277
|
+
# in-flight work, quieting, or draining on shutdown). Does **NOT** replace {#poll}, which
|
|
278
|
+
# drains the same queue while acquiring records; the "one share consumer per thread" rule
|
|
279
|
+
# means the two never run concurrently.
|
|
280
|
+
#
|
|
281
|
+
# @param timeout_ms [Integer] poll timeout. If set to 0 will run async, when set to -1 will
|
|
282
|
+
# block until any events available.
|
|
283
|
+
# @return [Integer] number of events served
|
|
284
|
+
# @raise [ClosedConsumerError] when the consumer is closed or closing
|
|
285
|
+
def events_poll(timeout_ms = Defaults::SHARE_CONSUMER_EVENTS_POLL_TIMEOUT_MS)
|
|
286
|
+
with_native(__method__) do |native|
|
|
287
|
+
# The main queue lives on the rd_kafka_t the share handle wraps (rkshare_rk), which is the
|
|
288
|
+
# handle librdkafka hands the callbacks; rd_kafka_poll has to run against it (see #name).
|
|
289
|
+
inner = Rdkafka::Bindings::NativeShareConsumer.new(native)[:rkshare_rk]
|
|
290
|
+
Rdkafka::Bindings.rd_kafka_poll(inner, timeout_ms)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Polls the main rdkafka queue without releasing the GVL (Global VM Lock).
|
|
295
|
+
#
|
|
296
|
+
# This is more efficient than {#events_poll} for non-blocking `poll(0)` calls, particularly
|
|
297
|
+
# useful in fiber scheduler contexts where GVL release/reacquire overhead is wasteful since we
|
|
298
|
+
# don't expect to wait.
|
|
299
|
+
#
|
|
300
|
+
# @param timeout_ms [Integer] poll timeout (default: 0 for non-blocking)
|
|
301
|
+
# @return [Integer] number of events served
|
|
302
|
+
# @raise [ClosedConsumerError] when the consumer is closed or closing
|
|
303
|
+
#
|
|
304
|
+
# @see #events_poll for more details on when to use this method
|
|
305
|
+
def events_poll_nb(timeout_ms = 0)
|
|
306
|
+
with_native(__method__) do |native|
|
|
307
|
+
inner = Rdkafka::Bindings::NativeShareConsumer.new(native)[:rkshare_rk]
|
|
308
|
+
Rdkafka::Bindings.rd_kafka_poll_nb(inner, timeout_ms)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
252
312
|
# Acknowledges a message delivered by {#poll} in explicit acknowledgement mode
|
|
253
313
|
# (`share.acknowledgement.mode` set to `explicit`).
|
|
254
314
|
#
|
data/lib/rdkafka/version.rb
CHANGED
data/renovate.json
CHANGED