karafka-rdkafka 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c14ac2b8e2474a4823811f9a61cf7da21345d41f2a9fa08931b9549c0a67798
4
- data.tar.gz: 31196228e2d00eb211c529c6daade3b378c3b707bc39aea8dc93bad5902b1fff
3
+ metadata.gz: 290a1e79997166534339567e0f46bf560191141a22c754bdd67c656ea68594f4
4
+ data.tar.gz: 972ca0af039c92a2b68d6767214ede8c06d7da0eaffde72f98c79bfcef0b8943
5
5
  SHA512:
6
- metadata.gz: 4aca7ee63fdbc488e15c712c80ba94fd4b8a7ef7e82cd5a0923cb0dfe9170a742bd0c9bb98d0c31a431f0d39900c466012c09497514eb5a9a05d5545ddd6b0c8
7
- data.tar.gz: ad04a75550357443f46710f5884f56b95cccd6d9ac635e50768dcef33146944bb84e2a7f7fabf5870a48d3442b0f51c2d11f3eadda4251bd2880a0d602fe5477
6
+ metadata.gz: 33ba78845a20d938ad79d61c859b08303dfe28ffdbf6a0fa5e99184805fa1d842153c0d676ba6fa25640241b71b2188a65d8ac58208e78526a5e2e3eedc433ad
7
+ data.tar.gz: 92f7639fca0c51e06844f705285df809a8680315d44531953d6d9b0049aff2964a7633933c31522b8fd75f4bb1606cb13f6a1a5c1d097e8a3f7011b5f3e04083
data/CHANGELOG.md CHANGED
@@ -1,44 +1,78 @@
1
1
  # Rdkafka Changelog
2
2
 
3
- ## 0.29.0 (2026-09-13)
4
- - [Enhancement] Bump librdkafka to `2.15.0` (staying on `2.15.0` rather than `2.15.1` so users hitting a regression in `2.15.1` have a stable fallback). The karafka-only statistics filter patch was rebased onto `2.15.0` (upstream grew the config property index from `64 * 35` to `64 * 36`; the patch keeps extending it to `64 * 40`). Ported from rdkafka-ruby (#970).
3
+ ## 0.30.0 (2026-09-25)
4
+ - [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
+ - [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.
6
+ - [Enhancement] `RdkafkaError.build_from_c` now uses the human readable string librdkafka attaches to the `rd_kafka_error_t` instead of always using `rd_kafka_err2str`, improving diagnostics for every error-pointer path. It also destroys a code-0 error object instead of leaking it on the early return.
7
+ - [Enhancement] Extract the OAuthBearer token buffer/extension plumbing in `Helpers::OAuth` around two private native-call hooks so the share consumer overrides only the hooks instead of duplicating both public methods.
8
+ - [Fix] Make `ShareConsumer#close` safe against in-flight calls from other threads: closing now waits for operations in progress (mirroring `NativeKafka`), preventing a NULL handle from reaching librdkafka or a double destroy when two threads race `close`.
9
+ - [Fix] Keep the `ShareConsumer` native handle and its GC finalizer when `close`/`destroy` report an error. librdkafka leaves the instance intact in that case, so dropping the references beforehand leaked the client permanently; `close` is now retriable after such an error.
10
+ - [Fix] Pin the acknowledgement commit callback `FFI::Function` in the state holder shared with the `ShareConsumer` GC finalizer, preventing a use-after-free when an unclosed consumer is garbage collected and the finalizer-driven destroy flushes pending acknowledgements through it.
11
+ - [Fix] Make `ShareConsumer` fork-aware, mirroring `NativeKafka`. `rd_kafka_share_consumer_new` is backed by the same broker/main threads as any librdkafka client, which do not survive `fork`; the hand-rolled share lifecycle previously had no `@creator_pid` guard, so a process that forked after creating a share consumer would segfault (or deadlock) when the child's GC finalizer or `close` ran `rd_kafka_share_destroy` on the inherited handle. It now records the creating pid and reports `closed?` in any other process, so the finalizer and `close` skip the native teardown for an inherited handle while `with_native` rejects any use of it.
12
+ - [Fix] Scope `ShareConsumer#each`'s `ClosedConsumerError` rescue to the `poll` call only. It previously wrapped the `yield`, so a `ClosedConsumerError` raised from inside the caller's block was swallowed and silently ended iteration; the block's errors now propagate, and only the consumer actually closing ends the loop. Its docs also note that, like `#poll`, `#each` can yield an `RdkafkaError` for a record that fails to build.
13
+ - [Fix] Raise `ArgumentError` for `ShareConsumer#subscribe` without topics. librdkafka treats an empty share subscribe as an unsubscribe and returns no error (unlike the regular consumer), silently dropping the subscription.
14
+ - [Fix] Capture the librdkafka client name onto `ShareConsumer#name` from the OAuthBearer token refresh callback. The share handle has no native name accessor, which made the documented name-based routing of the global oauth callback impossible for share consumers.
15
+ - [Fix] Update `ext/build_common.sh` (precompiled builds) to librdkafka `2.15.0` and its tarball checksum; it still pinned `2.14.1`, whose tarball is no longer vendored.
16
+ - [Fix] Stop the `share_consumer_multi_member` integration spec from failing on redelivery. KIP-932 share groups are documented as at-least-once, so a record whose acquisition lock expires before it is acknowledged may be redelivered with an incremented `delivery_count`; the spec now tracks distinct payloads and fails only when a record is never delivered.
17
+ - [Fix] Apply the same at-least-once tolerance to the `share_consumer_explicit_ack` integration spec, which still asserted exactly one redelivered record. It now requires only the released record to be redelivered (with `delivery_count >= 2`), forbids only the rejected record, and tolerates an accepted record reappearing when its acquisition lock expired before the commit flushed the accept.
18
+ - [Fix] Add the `nofile` ulimit to `docker-compose-ssl.yml`, mirroring `docker-compose.yml`. The SSL CI broker runs the same topic-heavy suite (now enlarged by the share consumer specs) and could otherwise exhaust the default file-descriptor limit under the many short-lived topics.
19
+ - [Fix] Enable KIP-932 share groups on the macOS CI broker, whose KRaft `server.properties` never opted into the share rebalance protocol, so the one broker-backed share consumer spec received zero records on every macOS runner. `CONFLUENT_VERSION` is bumped to `8.3.0` and the download path is derived from it so a Renovate bump cannot desync them.
20
+ - [Enhancement] Add two share consumer integration specs: `share_consumer_implicit_ack_spec` (polling alone accepts the previous batch, and accepted records are not redelivered to a second member) and `share_consumer_multi_topic_spec` (one consumer subscribed to several topics receives every topic's records, attributed to the right topic). Both track distinct payloads so an at-least-once redelivery cannot end the consume loop early.
21
+ - [Fix] Stop the `statistics_unassigned_producer` integration spec from flaking on a metadata-propagation race: the freshly built producer could send before fetching the new topic's metadata, so the produce is now retried on `unknown_topic_or_part` and `leader_not_available`.
22
+ - [Fix] Also stabilize the `statistics_unassigned_producer` integration spec against a delivery wait that outlives its budget. With 1000 partitions librdkafka keeps the message queued while leaders are still being elected, so the handle stays pending and the wait raises `WaitTimeoutError` - a `RuntimeError` the spec's existing code-based rescue could never catch. The produce now uses the default 60s handle wait (matching the sibling `statistics_unassigned_*` specs, instead of a self-imposed 15s) and retries on that timeout too.
23
+ - [Fix] Stabilize the `consumer_memberid_clusterid_leak` integration spec against RSS measurement noise on Ruby 4.0. It now compacts the heap before sampling (matching the `to_native_tpl` leak spec) and the ceiling is 3 MB, still well under the ~5.7 MB a genuine per-call leak would cost.
24
+ - [Note] Share consumers emit the regular consumer statistics JSON (including `cgrp`; there is no share-specific section in librdkafka 2.15.0) through the usual `statistics_callback`, serviced from within `#poll`. The share fetch path bypasses the per-partition stats bookkeeping entirely, so the `topics` section carries no partition entries and the `statistics.unassigned.include` filter needs no share-consumer special-casing; per-partition share metrics are not exposed by librdkafka 2.15.0 at all.
25
+ - [Feature] Add `Admin#alter_consumer_group_offsets` and `Admin#delete_consumer_group_offsets` to set or clear a consumer group's committed offsets from the admin client. Ported from rdkafka-ruby (#983).
26
+ - [Enhancement] Bump librdkafka to `2.15.1` for OpenSSL and libcurl security fixes. It changes how IPv6 addresses are formatted and validated against broker certificates.
27
+ - [Enhancement] Bump the bundled zlib to `1.3.2`.
28
+ - [Enhancement] Bump the bundled MIT Kerberos (krb5) to `1.22.2`. Ported from rdkafka-ruby (#979).
29
+ - [Enhancement] Add `Rdkafka::Config.partitioner_key_uses_bytesize` to hash partition keys by byte length, matching other Kafka clients for multibyte keys. Defaults to `false`. Ported from rdkafka-ruby (#985).
30
+ - [Maintenance] Derive the zlib `CHECKSUMS` entry from `ZLIB_VERSION`.
31
+ - [Maintenance] Drop the unused `dist/openssl-3.0.16.tar.gz` build cache.
32
+ - [Fix] Honor the `isolation_level:` argument to `Admin#list_offsets`, which was silently ignored. Ported from rdkafka-ruby (#983).
33
+ - [Fix] Return correct results for every item of a multi-item topic, partition, group or ACL admin request, not just the first. Ported from rdkafka-ruby (#987).
34
+ - [Maintenance] Fix a use-after-free in the multi-item admin integration test that read result-name pointers after librdkafka had destroyed the background event, which made it flaky on newer glibc (e.g. Debian trixie). Ported from rdkafka-ruby (#991).
35
+ - [Maintenance] Stabilize consumer specs on slow CI runners: wait longer for a partition assignment, raise the `TestTopics.create` admin timeout (a timeout there also tripped the leaked-handle guard), give the non-blocking `poll_nb` message check a generous retry budget instead of a fixed ~2s, and let the long-running consumption spec drain its backlog instead of stopping at a fixed 60s. The assignment wait is tunable via `RDKAFKA_TEST_ASSIGNMENT_TIMEOUT`. Ported from rdkafka-ruby (#990).
36
+
37
+ ## 0.29.0 (2026-09-14)
38
+ - [Enhancement] Bump librdkafka to `2.15.0` (staying on `2.15.0` rather than `2.15.1` so users hitting a regression in `2.15.1` have a stable fallback).
5
39
 
6
40
  ## 0.28.2 (2026-09-11)
7
- - [Fix] Close live clients from an `at_exit` hook before Ruby's shutdown finalization, so librdkafka is no longer `dlclose`d while its native threads are still running (which could segfault on exit). Ported from rdkafka-ruby (#964, Alex Selesse).
8
- - [Fix] Make client construction exception-safe and destroy the native handle when setup fails after `rd_kafka_new`, so a later error no longer orphans the native client. Ported from rdkafka-ruby (#964, Alex Selesse).
9
- - [Fix] Destroy each polled message exactly once in `Consumer#poll_batch`/`#poll_batch_nb` when building a message raises a non-`RdkafkaError`, fixing a double-free that could abort the process and closing the matching leak window. Ported from rdkafka-ruby (#973).
10
- - [Fix] Free the background queue, `AdminOptions` and any already-built `ConfigResource`s (and remove the handle) when `Admin#describe_configs`/`#incremental_alter_configs` raise while building native resources (e.g. a non-String resource name). Ported from rdkafka-ruby (#973).
11
- - [Fix] Raise a clear `ConfigError` instead of segfaulting when `Admin#describe_configs`/`#incremental_alter_configs` are given an empty resource name or a negative resource type (`rd_kafka_ConfigResource_new` returns NULL), or when `rd_kafka_AdminOptions_new` returns NULL. Ported from rdkafka-ruby (#973).
12
- - [Fix] Allocate the topic-partition metadata string with a process-global malloc instead of an autorelease-off `FFI::MemoryPointer`, so the buffer handed to librdkafka is not freed under it. Ported from rdkafka-ruby (#969, Randy Stauner).
41
+ - [Fix] Close live clients before Ruby shutdown finalization, preventing a possible segfault on exit. Ported from rdkafka-ruby (#964, Alex Selesse).
42
+ - [Fix] Destroy the native handle when client construction fails partway, so it is no longer orphaned. Ported from rdkafka-ruby (#964, Alex Selesse).
43
+ - [Fix] Fix a double-free in `Consumer#poll_batch`/`#poll_batch_nb` that could abort the process when building a message raised a non-`RdkafkaError`. Ported from rdkafka-ruby (#973).
44
+ - [Fix] Free native resources when `Admin#describe_configs`/`#incremental_alter_configs` raise while building their request (e.g. a non-String resource name). Ported from rdkafka-ruby (#973).
45
+ - [Fix] Raise `ConfigError` instead of segfaulting when `Admin#describe_configs`/`#incremental_alter_configs` get an empty resource name or a negative resource type. Ported from rdkafka-ruby (#973).
46
+ - [Fix] Stop the topic-partition metadata string from being freed while librdkafka still uses it. Ported from rdkafka-ruby (#969, Randy Stauner).
13
47
  - [Maintenance] Bump the bundled OpenSSL used by the precompiled builds to `3.5.8` (LTS). Ported from rdkafka-ruby (#972, Scott Francis).
14
48
 
15
49
  ## v0.28.1 (2026-09-04)
16
- - [Enhancement] Add `Admin#delete_records`, wrapping librdkafka's `DeleteRecords` admin API. Deletes all messages in a partition up to (but not including) a given offset - accepts an integer offset or `:end` to clear all current data. Ported from rdkafka-ruby (#956).
17
- - [Enhancement] Add `Admin#list_consumer_groups`, wrapping librdkafka's `ListConsumerGroups` admin API. Returns a cluster-wide listing of consumer groups (`group_id`, `is_simple_consumer_group`, state) plus any per-broker errors. Ported from rdkafka-ruby (#955).
50
+ - [Enhancement] Add `Admin#delete_records` to delete messages in a partition up to a given offset, or all current data with `:end`. Ported from rdkafka-ruby (#956).
51
+ - [Enhancement] Add `Admin#list_consumer_groups` for a cluster-wide listing of consumer groups. Ported from rdkafka-ruby (#955).
18
52
  - [Fix] Make `NativeKafka#close` fork-aware so a forked child no longer segfaults on exit. Handles record their creator pid and skip the native teardown in any other process.
19
53
  - [Fix] Stabilize the flaky partitions count cache statistics spec.
20
54
  - [Fix] Stabilize the `to_native_tpl` leak integration spec against RSS measurement noise.
21
55
 
22
56
  ## v0.28.0 (2026-07-12)
23
- - [Enhancement] Bump librdkafka to `2.14.2`. Maintenance release: fixes duplicate groups in `ListConsumerGroups` when multiple brokers return the same group, a data race in timers, and bumps bundled OpenSSL/libcurl/zstd/zlib/cJSON dependencies (several CVEs).
24
- - [Enhancement] Add `Consumer#metadata` and `Producer#metadata`, mirroring `Admin#metadata`, so cluster/topic metadata can be fetched from an existing consumer or producer handle without opening a dedicated admin connection.
25
- - [Enhancement] Name the failing partition and topic in the `RdkafkaError` raised for per-partition `list_offsets` errors (previously a bare error code), preserving the per-partition context the pre-batching `Consumer#lag` watermark errors carried.
26
- - [Enhancement] Add `Consumer#list_offsets`, mirroring `Admin#list_offsets`, so offsets can be queried on an existing consumer handle without a dedicated admin connection, and rebuild `Consumer#lag` on top of it as one batched query instead of a roundtrip per partition.
57
+ - [Enhancement] Bump librdkafka to `2.14.2` (maintenance release with bundled dependency CVE fixes and a fix for duplicate groups in `ListConsumerGroups`).
58
+ - [Enhancement] Add `Consumer#metadata` and `Producer#metadata`, mirroring `Admin#metadata`, to fetch cluster metadata without a separate admin connection.
59
+ - [Enhancement] Name the failing topic and partition in `RdkafkaError`s raised for per-partition `list_offsets` errors.
60
+ - [Enhancement] Add `Consumer#list_offsets`, mirroring `Admin#list_offsets`, and compute `Consumer#lag` with one batched query instead of a roundtrip per partition.
27
61
  - [Enhancement] Extract the admin background-event result handlers into one class per operation under `lib/rdkafka/callbacks/`. Internal reorganization with no API or behavior change.
28
62
  - [Enhancement] Expose `replicas` and `isrs` (in-sync replica broker ids) on each partition in topic metadata; both were previously dropped from the `Metadata#topics` partition hashes.
29
63
  - [Enhancement] Reuse per-thread scratch pointers in `Consumer::Headers.from_native`, removing the per-message native allocations from the consumer hot path.
30
64
  - [Enhancement] Remove the unused `DeliveryHandle` `:topic_name` struct field and its per-message allocation. Use `DeliveryHandle#topic` or `DeliveryReport#topic_name`, both unchanged.
31
- - [Fix] Stop `poll_batch`/`poll_batch_nb` from discarding a whole batch when one message fails to build. The failure is now surfaced inline as an `RdkafkaError` in the returned array, preserving the rest of the batch.
65
+ - [Fix] Stop `poll_batch`/`poll_batch_nb` from discarding a whole batch when one message fails to build. The failure is returned inline as an `RdkafkaError`.
32
66
  - [Fix] Add the missing `closed_consumer_check` to `Consumer#position`, so it raises a consistent `ClosedConsumerError` like every sibling offset method.
33
67
  - [Fix] Stop leaking the native `rd_kafka_topic_conf_t` in `Producer#set_topic_config` when a per-topic config value is rejected.
34
68
  - [Fix] Raise instead of silently dropping a rejected `incremental_alter_configs` entry, which previously left the alter request reporting success.
35
69
  - [Fix] Let `PartitionsCountCache` adopt a lower partition count once the cached entry has expired, so a topic recreated with fewer partitions no longer fails `produce` until process restart.
36
- - [Fix] Make the `Consumer` GC finalizer close the consumer and destroy its consumer queue, not just the native client, preventing a hang or leak when a consumer is collected without an explicit `close`.
37
- - [Fix] Stop `describe_configs`, `incremental_alter_configs` and `list_offsets` from leaking handles and native resources when their arguments are rejected; all three now validate input before allocating.
70
+ - [Fix] Fully close a consumer collected without an explicit `close`, preventing a hang or leak.
71
+ - [Fix] Stop `describe_configs`, `incremental_alter_configs` and `list_offsets` from leaking native resources when their arguments are rejected.
38
72
  - [Fix] Destroy the native topic-partition list in `TopicPartitionList#to_native_tpl` when population fails partway, which previously leaked the half-built list.
39
73
  - [Fix] Allocate the admin result-count out-parameter as `:size_t` instead of `:int32`, fixing a 4-byte overflow on every admin result parse.
40
- - [Fix] Destroy admin API background events after processing; they were never destroyed, so every admin operation leaked its whole result event. The internal FFI struct fields on admin handles were removed as part of this - use `handle.wait` and the returned report objects, whose interfaces are unchanged.
41
- - [Fix] Resolve admin operation handles from the event error when an operation fails at the operation level (e.g. brokers unreachable), instead of blocking until `wait` timed out and discarding the real error.
74
+ - [Fix] Stop every admin operation from leaking its result event. The internal FFI struct fields on admin handles are removed; use `handle.wait` and the returned report objects, which are unchanged.
75
+ - [Fix] Return the real error from an admin operation that fails at the operation level (e.g. brokers unreachable) instead of blocking until `wait` times out.
42
76
  - [Fix] Stop leaking the native `rd_kafka_conf_t` when client creation fails, a multi-KB leak per failed attempt for supervisors retrying on transient SASL/SSL misconfiguration.
43
77
  - [Fix] Stop `Metadata` from leaking the native metadata struct on every retried fetch; each attempt now frees its own native resources.
44
78
  - [Fix] Free the librdkafka-allocated string in `Consumer#cluster_id` and `Consumer#member_id`, and fix the `rd_kafka_clusterid` arity. `Consumer#cluster_id` now accepts a `timeout_ms`.
@@ -46,10 +80,10 @@
46
80
  - [Fix] Stop `Producer#produce` from orphaning the delivery handle in the process-global registry when it fails after registering it.
47
81
  - [Fix] Attach `rd_kafka_query_watermark_offsets` with `blocking: true` so it releases the GVL; it previously froze every other Ruby thread for up to `timeout_ms`.
48
82
  - [Fix] Stabilize the flaky `Consumer#lag` spec on overloaded CI. Backported from rdkafka-ruby (#912).
49
- - [Fix] Fix the NULL background-queue cleanup branches in `Admin#delete_group`, `Admin#delete_acl` and `Admin#describe_acl`, which raised a `NameError` and leaked the native request object instead of raising `ConfigError`.
83
+ - [Fix] Raise `ConfigError` instead of `NameError` in `Admin#delete_group`, `#delete_acl` and `#describe_acl` when the background queue is unavailable.
50
84
  - [Fix] Forward `broker_message` and `instance_name` through `RdkafkaError.build`; both were previously discarded on the `rd_kafka_error_t` pointer path.
51
85
  - [Fix] Synchronize `AbstractHandle::REGISTRY` mutations with a mutex, which could otherwise lose a write on JRuby and leave a handle unregistered or leaked.
52
- - [Fix] Stop the `Metadata` retry loop from clobbering the request timeout and blocking for minutes. The request timeout is left unchanged across retries, the backoff is capped, and the loop is bounded by a ~5s wall-clock budget after a floor of 3 attempts.
86
+ - [Fix] Bound the `Metadata` retry loop to about 5 seconds, so a synchronous metadata fetch can no longer block for minutes.
53
87
  - [Fix] Cache the partition count for a missing topic, so `produce` with a `partition_key` to a not-yet-created topic no longer runs a blocking metadata query on every message.
54
88
 
55
89
  ## 0.27.2 (2026-05-21)
data/README.md CHANGED
@@ -63,7 +63,8 @@ Contributions should generally be made to the upstream [rdkafka-ruby repository]
63
63
 
64
64
  | karafka-rdkafka | librdkafka | patches |
65
65
  |-|-|-|
66
- | 0.29.x (2026-09-13) | 2.15.0 (2026-06-30) | yes |
66
+ | 0.30.x (2026-09-25) | 2.15.1 (2026-09-09) | yes |
67
+ | 0.29.x (2026-09-14) | 2.15.0 (2026-06-30) | yes |
67
68
  | 0.28.x (2026-07-12) | 2.14.2 (2026-06-03) | yes |
68
69
  | 0.27.x (2026-05-08) | 2.14.1 (2026-04-15) | yes |
69
70
  | 0.26.x (2026-04-11) | 2.14.0 (2026-04-01) | yes |
@@ -14,6 +14,8 @@ allowed_patterns=(
14
14
  "closing connection"
15
15
  "sent a heartbeat request but received error REQUEST_TIMED_OUT"
16
16
  "Topic '__consumer_offsets' already exists"
17
+ "is deprecated. Share groups are controlled by the 'share.version' feature"
18
+ "is deprecated and will be removed in Kafka 5.0. The following protocol(s) are currently disabled"
17
19
  )
18
20
 
19
21
  # Get all warnings
@@ -7,6 +7,14 @@ services:
7
7
  - 9093:9093
8
8
  volumes:
9
9
  - ./ssl:/etc/kafka/secrets
10
+
11
+ # The suite creates hundreds of short-lived topics; each keeps several segment/index file
12
+ # descriptors open on the broker
13
+ ulimits:
14
+ nofile:
15
+ soft: 262144
16
+ hard: 262144
17
+
10
18
  environment:
11
19
  CLUSTER_ID: kafka-docker-cluster-1
12
20
  KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
@@ -25,6 +33,11 @@ services:
25
33
  KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true"
26
34
  KAFKA_AUTHORIZER_CLASS_NAME: org.apache.kafka.metadata.authorizer.StandardAuthorizer
27
35
 
36
+ # KIP-932 share groups (queues for Kafka) used by the share consumer specs
37
+ KAFKA_GROUP_COORDINATOR_REBALANCE_PROTOCOLS: classic,consumer,share
38
+ KAFKA_SHARE_COORDINATOR_STATE_TOPIC_REPLICATION_FACTOR: 1
39
+ KAFKA_SHARE_COORDINATOR_STATE_TOPIC_MIN_ISR: 1
40
+
28
41
  # SSL Configuration
29
42
  KAFKA_SSL_KEYSTORE_FILENAME: kafka.server.keystore.jks
30
43
  KAFKA_SSL_KEYSTORE_CREDENTIALS: kafka_keystore_creds
data/docker-compose.yml CHANGED
@@ -6,6 +6,13 @@ services:
6
6
  ports:
7
7
  - 9092:9092
8
8
 
9
+ # The suite creates hundreds of short-lived topics; each keeps several segment/index file
10
+ # descriptors open on the broker
11
+ ulimits:
12
+ nofile:
13
+ soft: 262144
14
+ hard: 262144
15
+
9
16
  environment:
10
17
  CLUSTER_ID: kafka-docker-cluster-1
11
18
  KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
@@ -23,3 +30,7 @@ services:
23
30
  KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
24
31
  KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true"
25
32
  KAFKA_AUTHORIZER_CLASS_NAME: org.apache.kafka.metadata.authorizer.StandardAuthorizer
33
+ # KIP-932 share groups (queues for Kafka) used by the share consumer integration specs
34
+ KAFKA_GROUP_COORDINATOR_REBALANCE_PROTOCOLS: classic,consumer,share
35
+ KAFKA_SHARE_COORDINATOR_STATE_TOPIC_REPLICATION_FACTOR: 1
36
+ KAFKA_SHARE_COORDINATOR_STATE_TOPIC_MIN_ISR: 1
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ class Admin
5
+ # Handle for alter consumer group offsets operation
6
+ class AlterConsumerGroupOffsetsHandle < AbstractHandle
7
+ layout :pending, :bool,
8
+ :response, :int
9
+
10
+ # @return [String] the name of the operation
11
+ def operation_name
12
+ "alter consumer group offsets"
13
+ end
14
+
15
+ # Creates the result report
16
+ # @return [AlterConsumerGroupOffsetsReport]
17
+ def create_result
18
+ prepared_result
19
+ end
20
+
21
+ # Raises an error if the operation failed
22
+ # @raise [RdkafkaError]
23
+ def raise_error
24
+ raise RdkafkaError.new(
25
+ self[:response],
26
+ broker_message: broker_message
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ class Admin
5
+ # Report for alter consumer group offsets operation result
6
+ class AlterConsumerGroupOffsetsReport
7
+ # @return [String, nil] the group the operation ran against
8
+ attr_reader :group_name
9
+
10
+ # @return [String, nil] group level error message, if any
11
+ attr_reader :error_string
12
+
13
+ # Per partition outcomes, each with `:topic`, `:partition`, `:offset` and `:error`
14
+ # (an `RdkafkaError` or nil). Partitions are reported individually because Kafka can
15
+ # fail some and accept others within a single request.
16
+ # @return [Array<Hash>]
17
+ attr_reader :partitions
18
+
19
+ # @param error_string [FFI::Pointer] pointer to the group level error string
20
+ # @param result_name [FFI::Pointer] pointer to the group name
21
+ # @param partitions_ptr [FFI::Pointer] pointer to the per partition result list
22
+ def initialize(error_string, result_name, partitions_ptr = FFI::Pointer::NULL)
23
+ @error_string = error_string.read_string if error_string != FFI::Pointer::NULL
24
+ @group_name = result_name.read_string if result_name != FFI::Pointer::NULL
25
+ @partitions = extract_partitions(partitions_ptr)
26
+ end
27
+
28
+ private
29
+
30
+ # @param partitions_ptr [FFI::Pointer] pointer to a native topic partition list
31
+ # @return [Array<Hash>]
32
+ def extract_partitions(partitions_ptr)
33
+ return [] if partitions_ptr.nil? || partitions_ptr.null?
34
+
35
+ native = Bindings::TopicPartitionList.new(partitions_ptr)
36
+
37
+ Array.new(native[:cnt]) do |i|
38
+ tp = Bindings::TopicPartition.new(native[:elems] + (i * Bindings::TopicPartition.size))
39
+
40
+ {
41
+ topic: tp[:topic],
42
+ partition: tp[:partition],
43
+ offset: tp[:offset],
44
+ error: tp[:err].zero? ? nil : RdkafkaError.new(tp[:err])
45
+ }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ class Admin
5
+ # Handle for delete consumer group offsets operation
6
+ class DeleteConsumerGroupOffsetsHandle < AbstractHandle
7
+ layout :pending, :bool,
8
+ :response, :int
9
+
10
+ # @return [String] the name of the operation
11
+ def operation_name
12
+ "delete consumer group offsets"
13
+ end
14
+
15
+ # Creates the result report
16
+ # @return [DeleteConsumerGroupOffsetsReport]
17
+ def create_result
18
+ prepared_result
19
+ end
20
+
21
+ # Raises an error if the operation failed
22
+ # @raise [RdkafkaError]
23
+ def raise_error
24
+ raise RdkafkaError.new(
25
+ self[:response],
26
+ broker_message: broker_message
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdkafka
4
+ class Admin
5
+ # Report for delete consumer group offsets operation result
6
+ class DeleteConsumerGroupOffsetsReport
7
+ # @return [String, nil] the group the operation ran against
8
+ attr_reader :group_name
9
+
10
+ # @return [String, nil] group level error message, if any
11
+ attr_reader :error_string
12
+
13
+ # Per partition outcomes, each with `:topic`, `:partition`, `:offset` and `:error`
14
+ # (an `RdkafkaError` or nil). Partitions are reported individually because Kafka can
15
+ # fail some and accept others within a single request.
16
+ # @return [Array<Hash>]
17
+ attr_reader :partitions
18
+
19
+ # @param error_string [FFI::Pointer] pointer to the group level error string
20
+ # @param result_name [FFI::Pointer] pointer to the group name
21
+ # @param partitions_ptr [FFI::Pointer] pointer to the per partition result list
22
+ def initialize(error_string, result_name, partitions_ptr = FFI::Pointer::NULL)
23
+ @error_string = error_string.read_string if error_string != FFI::Pointer::NULL
24
+ @group_name = result_name.read_string if result_name != FFI::Pointer::NULL
25
+ @partitions = extract_partitions(partitions_ptr)
26
+ end
27
+
28
+ private
29
+
30
+ # @param partitions_ptr [FFI::Pointer] pointer to a native topic partition list
31
+ # @return [Array<Hash>]
32
+ def extract_partitions(partitions_ptr)
33
+ return [] if partitions_ptr.nil? || partitions_ptr.null?
34
+
35
+ native = Bindings::TopicPartitionList.new(partitions_ptr)
36
+
37
+ Array.new(native[:cnt]) do |i|
38
+ tp = Bindings::TopicPartition.new(native[:elems] + (i * Bindings::TopicPartition.size))
39
+
40
+ {
41
+ topic: tp[:topic],
42
+ partition: tp[:partition],
43
+ offset: tp[:offset],
44
+ error: tp[:err].zero? ? nil : RdkafkaError.new(tp[:err])
45
+ }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
data/lib/rdkafka/admin.rb CHANGED
@@ -224,6 +224,78 @@ module Rdkafka
224
224
  create_topic_handle
225
225
  end
226
226
 
227
+ # Sets the committed offsets of a consumer group from outside the group.
228
+ #
229
+ # Kafka requires the group to be empty (no active members) for this to be accepted; when it
230
+ # is not, the broker rejects the request and the handle raises with `group_not_empty` rather
231
+ # than reporting success.
232
+ #
233
+ # @param group_id [String] the group whose offsets we want to set
234
+ # @param topic_partition_list [TopicPartitionList] partitions with the offsets to commit
235
+ # @return [AlterConsumerGroupOffsetsHandle] handle that can be used to wait for the result
236
+ # @raise [RdkafkaError] when altering the offsets fails
237
+ def alter_consumer_group_offsets(group_id, topic_partition_list)
238
+ closed_admin_check(__method__)
239
+
240
+ native_tpl = topic_partition_list.to_native_tpl
241
+
242
+ begin
243
+ alter_ptr = Rdkafka::Bindings.rd_kafka_AlterConsumerGroupOffsets_new(
244
+ FFI::MemoryPointer.from_string(group_id),
245
+ native_tpl
246
+ )
247
+ rescue Exception
248
+ Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(native_tpl)
249
+ raise
250
+ end
251
+
252
+ # librdkafka copies the list into the request object, so ours is no longer needed
253
+ Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(native_tpl)
254
+
255
+ enqueue_group_offsets_op(
256
+ request_ptr: alter_ptr,
257
+ handle: AlterConsumerGroupOffsetsHandle.new,
258
+ admin_op: Rdkafka::Bindings::RD_KAFKA_ADMIN_OP_ALTERCONSUMERGROUPOFFSETS,
259
+ destroy: Rdkafka::Bindings.method(:rd_kafka_AlterConsumerGroupOffsets_destroy),
260
+ enqueue: Rdkafka::Bindings.method(:rd_kafka_AlterConsumerGroupOffsets)
261
+ )
262
+ end
263
+
264
+ # Deletes the committed offsets of specific partitions of a consumer group, leaving the
265
+ # group and its remaining offsets in place.
266
+ #
267
+ # This is the partition scoped counterpart of {#delete_group}, which removes the whole group.
268
+ #
269
+ # @param group_id [String] the group whose offsets we want to delete
270
+ # @param topic_partition_list [TopicPartitionList] partitions whose offsets to delete
271
+ # @return [DeleteConsumerGroupOffsetsHandle] handle that can be used to wait for the result
272
+ # @raise [RdkafkaError] when deleting the offsets fails
273
+ def delete_consumer_group_offsets(group_id, topic_partition_list)
274
+ closed_admin_check(__method__)
275
+
276
+ native_tpl = topic_partition_list.to_native_tpl
277
+
278
+ begin
279
+ delete_ptr = Rdkafka::Bindings.rd_kafka_DeleteConsumerGroupOffsets_new(
280
+ FFI::MemoryPointer.from_string(group_id),
281
+ native_tpl
282
+ )
283
+ rescue Exception
284
+ Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(native_tpl)
285
+ raise
286
+ end
287
+
288
+ Rdkafka::Bindings.rd_kafka_topic_partition_list_destroy(native_tpl)
289
+
290
+ enqueue_group_offsets_op(
291
+ request_ptr: delete_ptr,
292
+ handle: DeleteConsumerGroupOffsetsHandle.new,
293
+ admin_op: Rdkafka::Bindings::RD_KAFKA_ADMIN_OP_DELETECONSUMERGROUPOFFSETS,
294
+ destroy: Rdkafka::Bindings.method(:rd_kafka_DeleteConsumerGroupOffsets_destroy),
295
+ enqueue: Rdkafka::Bindings.method(:rd_kafka_DeleteConsumerGroupOffsets)
296
+ )
297
+ end
298
+
227
299
  # Deletes a consumer group
228
300
  #
229
301
  # @param group_id [String] the group id to delete
@@ -1134,6 +1206,54 @@ module Rdkafka
1134
1206
 
1135
1207
  private
1136
1208
 
1209
+ # Shared enqueue path for the two consumer group offset admin operations. They differ only
1210
+ # in their C entry points, so keeping one body avoids two near identical copies of the
1211
+ # queue/handle/options dance and its cleanup.
1212
+ #
1213
+ # @param request_ptr [FFI::Pointer] the op specific request object
1214
+ # @param handle [AbstractHandle] the handle to register and return
1215
+ # @param admin_op [Integer] the RD_KAFKA_ADMIN_OP_* constant for this operation
1216
+ # @param destroy [Method] bound function used to free the request object
1217
+ # @param enqueue [Method] bound function used to enqueue the request
1218
+ # @return [AbstractHandle] the registered handle
1219
+ def enqueue_group_offsets_op(request_ptr:, handle:, admin_op:, destroy:, enqueue:)
1220
+ array_ptr = FFI::MemoryPointer.new(:pointer)
1221
+ array_ptr.write_array_of_pointer([request_ptr])
1222
+
1223
+ queue_ptr = @native_kafka.with_inner do |inner|
1224
+ Rdkafka::Bindings.rd_kafka_queue_get_background(inner)
1225
+ end
1226
+
1227
+ if queue_ptr.null?
1228
+ destroy.call(request_ptr)
1229
+ raise Rdkafka::Config::ConfigError.new("rd_kafka_queue_get_background was NULL")
1230
+ end
1231
+
1232
+ handle[:pending] = true
1233
+ handle[:response] = Rdkafka::Bindings::RD_KAFKA_PARTITION_UA
1234
+ handle.class.register(handle)
1235
+
1236
+ admin_options_ptr = @native_kafka.with_inner do |inner|
1237
+ Rdkafka::Bindings.rd_kafka_AdminOptions_new(inner, admin_op)
1238
+ end
1239
+ Rdkafka::Bindings.rd_kafka_AdminOptions_set_opaque(admin_options_ptr, handle.to_ptr)
1240
+
1241
+ begin
1242
+ @native_kafka.with_inner do |inner|
1243
+ enqueue.call(inner, array_ptr, 1, admin_options_ptr, queue_ptr)
1244
+ end
1245
+ rescue Exception
1246
+ handle.class.remove(handle.to_ptr.address)
1247
+ raise
1248
+ ensure
1249
+ Rdkafka::Bindings.rd_kafka_AdminOptions_destroy(admin_options_ptr)
1250
+ Rdkafka::Bindings.rd_kafka_queue_destroy(queue_ptr)
1251
+ destroy.call(request_ptr)
1252
+ end
1253
+
1254
+ handle
1255
+ end
1256
+
1137
1257
  # Checks if the admin is closed and raises an error if so
1138
1258
  # @param method [Symbol] name of the calling method for error context
1139
1259
  # @raise [ClosedAdminError] when the admin is closed