Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions .github/workflows/build-confluent-kafka.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# The prebuilt librdkafka.redist NuGet upstream normally uses has no riscv64 build, so this
# follows their other supported path: tools/cibuildwheel-build.sh, which builds it from source.
name: Build confluent-kafka wheels (riscv64)

on:
workflow_dispatch:
inputs:
version:
description: 'confluent-kafka version to build (e.g. 2.15.0)'
required: true
default: '2.15.0'
pull_request:
paths:
- '.github/workflows/build-confluent-kafka.yml'
- 'patches/confluent-kafka/**'

concurrency:
group: ${{ github.workflow }}-${{ inputs.version || '2.15.0' }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

permissions:
contents: read # to fetch code (actions/checkout)

env:
CONFLUENT_KAFKA_VERSION: ${{ inputs.version || '2.15.0' }}
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64

jobs:
build_wheels:
name: Build confluent-kafka ${{ inputs.version || '2.15.0' }} ${{ matrix.python }}-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: 360
strategy:
fail-fast: false
matrix:
# No cp314t: upstream's build-wheels.sh skips `*t-*`.
python:
- "cp312"
- "cp313"
- "cp314"

steps:
- name: Checkout confluent-kafka-python ${{ env.CONFLUENT_KAFKA_VERSION }}
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: confluentinc/confluent-kafka-python
ref: v${{ env.CONFLUENT_KAFKA_VERSION }}
persist-credentials: false

- name: Checkout python-wheels
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
path: python-wheels
persist-credentials: false

- name: Patch confluent-kafka source
run: |
git apply python-wheels/patches/confluent-kafka/${{ env.CONFLUENT_KAFKA_VERSION }}/00*.patch

- name: Resolve the librdkafka version this release pins
id: librdkafka
# Same extraction upstream's tools/pre-release-validation.sh uses.
run: |
version="$(sed -n '/LIBRDKAFKA_VERSION/{n;s/.*value: *//p;}' .semaphore/semaphore.yml | head -1)"
echo "version=$version" >> "$GITHUB_OUTPUT"

- name: Build wheels
uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
with:
output-dir: wheelhouse/
only: ${{ matrix.python }}-manylinux_riscv64
env:
CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }}
CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/
# OpenSSL's Configure needs full perl; the image ships only perl-interpreter.
CIBW_BEFORE_BUILD_LINUX: >-
dnf -y install perl &&
tools/prepare-cibuildwheel-linux.sh ${{ steps.librdkafka.outputs.version }}
# A source-built librdkafka keeps its debug symbols (58MB vs upstream's prebuilt 11MB).
CIBW_REPAIR_WHEEL_COMMAND: auditwheel repair --strip -w {dest_dir} {wheel}
# Wider than upstream's single file: all of tests/ bar integration (needs a Kafka cluster)
# and schema_registry (needs tink/google-re2, no riscv64 build).
CIBW_TEST_EXTRAS: oauthbearer-aws
CIBW_TEST_REQUIRES: avro requests
CIBW_BEFORE_TEST: pip install -r {project}/requirements/requirements-tests.txt
# Asserts the LICENSE patch reached the wheel; a patch that stops applying is otherwise silent.
CIBW_TEST_COMMAND: >-
python -c "import importlib.metadata as m, pathlib; p = [f for f in m.files('confluent-kafka') if '.dist-info/licenses/' in str(f)]; t = pathlib.Path(m.distribution('confluent-kafka').locate_file(p[0])).read_text(); assert 'For Zstandard software' in t and 'daniel@haxx.se' in t, p" &&
pytest --timeout 1200 {project}/tests
--ignore={project}/tests/integration
--ignore={project}/tests/schema_registry

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: confluent-kafka-${{ env.CONFLUENT_KAFKA_VERSION }}-${{ matrix.python }}-manylinux_riscv64
path: wheelhouse/*.whl
if-no-files-found: error

publish:
name: Publish confluent-kafka ${{ inputs.version || '2.15.0' }} to GitLab
needs: [build_wheels]
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
- name: Publish wheels and open docs PR
uses: riseproject-dev/python-wheels/actions/publish-wheels@main
with:
artifact-pattern: confluent-kafka-${{ env.CONFLUENT_KAFKA_VERSION }}-*-manylinux_riscv64
gitlab-username: ${{ vars.GITLAB_DEPLOY_USER }}
gitlab-token: ${{ secrets.GITLAB_DEPLOY_TOKEN }}
gitlab-project-id: ${{ vars.GITLAB_PROJECT_ID }}
gh-token: ${{ secrets.GITHUB_TOKEN }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
From 0497677f40514668b5aba9885d368f3de437bff0 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Wed, 26 Aug 2026 02:29:29 +0200
Subject: [PATCH] add zstd's and curl's licences to the binary wheel's LICENSE

Upstream-Status: To upstream [not submitted yet: confluentinc requires a signed CLA before a pull request can be accepted]

The Linux and macOS wheels statically link libzstd and libcurl into
librdkafka (`STATIC_LINKING ... ZSTD CURL` in the shipped
librdkafka-*.so.1, zstd 1.5.7 and curl 8.20.0 as pinned by librdkafka's
mklove source-deps). LICENSE already carries the extra notices the binary
wheel needs -- a dedicated "The binary wheel distribution of
confluent-kafka-python contains additional software with the following
licenses" section covering OpenSSL, zlib and librdkafka -- but zstd and
curl are missing from it.

Both licences require their copyright notice to travel with binary
redistributions, so add them beside the ones already listed.
---
LICENSE | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 68 insertions(+)

diff --git a/LICENSE b/LICENSE
index 02f2aa0..266a23f 100644
--- a/LICENSE
+++ b/LICENSE
@@ -364,6 +364,74 @@ OpenSSL License



+##############################################################################
+# zstd #
+##############################################################################
+
+BSD License
+
+For Zstandard software
+
+Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name Facebook, nor Meta, nor the names of its contributors may
+ be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
+
+##############################################################################
+# curl #
+##############################################################################
+
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright (c) 1996 - 2026, Daniel Stenberg, <daniel@haxx.se>, and many
+contributors, see the THANKS file.
+
+All rights reserved.
+
+Permission to use, copy, modify, and distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright
+notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN
+NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+OR OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder shall not
+be used in advertising or otherwise to promote the sale, use or other dealings
+in this Software without prior written authorization of the copyright holder.
+
+
+
+
##############################################################################
# librdkafka licenses #
##############################################################################
--
2.50.1 (Apple Git-155)

Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
From f12ffc38c16c3a9e94985cd7b048dd5cff3b8ad2 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Wed, 26 Aug 2026 12:54:40 +0200
Subject: [PATCH] tests: make the raising callbacks in
test_callback_exception_no_system_error raise once

test_callback_exception_no_system_error installs an error_cb and a
stats_cb that raise unconditionally, asserts the exception surfaces out
of consume(), and then calls close() with the callback still armed.
librdkafka keeps delivering both events for the lifetime of the consumer
-- stats every statistics.interval.ms (100ms here), the broker-resolve
failure on its retry backoff -- and close() dispatches whatever is queued,
so if more than one interval elapses between the assertions and close()
the callback fires again and the RuntimeError escapes close(), failing the
test.

It is a race, not an architecture problem: on a slower CI runner the few
statements between the assertion and close() are enough. Reproduced on
macOS/arm64 by inserting time.sleep(1.2) before each close() -- the test
fails there unmodified and passes with this change.

Raise only on the first invocation. The assertions are unchanged
(each callback still runs, still appends, still raises out of consume());
only the redundant later raises during close() go away.

Upstream-Status: To upstream [not submitted yet: confluentinc requires a signed CLA before a pull request can be accepted]
---
tests/test_Consumer.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/tests/test_Consumer.py b/tests/test_Consumer.py
index 74b3120..ad42828 100644
--- a/tests/test_Consumer.py
+++ b/tests/test_Consumer.py
@@ -440,7 +440,9 @@ def test_callback_exception_no_system_error():
error_called = []

def error_cb_that_raises(error):
- """Error callback that raises an exception"""
+ """Error callback that raises an exception, once"""
+ if error_called:
+ return
error_called.append(error)
raise RuntimeError("Test exception from error_cb")

@@ -469,7 +471,9 @@ def test_callback_exception_no_system_error():
stats_called = []

def stats_cb_that_raises(stats_json):
- """Stats callback that raises an exception"""
+ """Stats callback that raises an exception, once"""
+ if stats_called:
+ return
stats_called.append(stats_json)
raise RuntimeError("Test exception from stats_cb")

--
2.50.1 (Apple Git-155)

Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
From 253e0c6cfd7a0f07e9c43ab686824dcc8bca1b4b Mon Sep 17 00:00:00 2001
From: Ojasva Jain <ojain@confluent.io>
Date: Mon, 24 Aug 2026 16:53:14 +0530
Subject: [PATCH] Fix reference counting in delete_records Admin API (#2328)


Backported to v2.15.0: the CHANGELOG.md hunk is dropped because that file has
moved on upstream and the entry describes a release we do not build; the
Admin.c fix and the regression test in tests/test_Admin.py apply unchanged.

Without it, Admin_delete_records() Py_XDECREFs topic_partition_offsets, a
borrowed reference it never incremented, on both the success and the error
path. The caller's list is freed while still referenced, and the interpreter
faults at whatever allocates next -- for us a SIGSEGV in re/_compiler.py in
the following test module on cp314. Reproduces off-arch on macOS/arm64
against the released 2.15.0 wheel, so it is not riscv64-specific.

Fixed upstream in commit 253e0c6cfd7a0f07e9c43ab686824dcc8bca1b4b, merged
2026-08-24 and not present in the 2.15.0 tag we build.

Upstream-Status: Backport [https://github.com/confluentinc/confluent-kafka-python/pull/2328]
---

diff --git a/src/confluent_kafka/src/Admin.c b/src/confluent_kafka/src/Admin.c
index 0c8a97070..10e76dfa1 100644
--- a/src/confluent_kafka/src/Admin.c
+++ b/src/confluent_kafka/src/Admin.c
@@ -3272,7 +3272,6 @@ PyObject *Admin_delete_records(Handle *self, PyObject *args, PyObject *kwargs) {
free(c_obj);

rd_kafka_topic_partition_list_destroy(c_topic_partition_offsets);
- Py_XDECREF(topic_partition_offsets);

Py_RETURN_NONE;
err:
@@ -3288,7 +3287,6 @@ PyObject *Admin_delete_records(Handle *self, PyObject *args, PyObject *kwargs) {
rd_kafka_topic_partition_list_destroy(
c_topic_partition_offsets);
}
- Py_XDECREF(topic_partition_offsets);
return NULL;
}

diff --git a/tests/test_Admin.py b/tests/test_Admin.py
index a6d67628a..0892bf03d 100644
--- a/tests/test_Admin.py
+++ b/tests/test_Admin.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python
import concurrent.futures
+import sys

import pytest

@@ -1177,6 +1178,31 @@ def test_delete_records():
a.delete_records([TopicPartition("test-topic1")])


+def test_delete_records_does_not_corrupt_argument_refcount():
+ """
+ Regression test for #2275.
+ delete_records() must not decref the 'topic_partition_offsets' argument it only borrows.
+ """
+ a = AdminClient({"socket.timeout.ms": 10})
+ request = [TopicPartition("test-topic1", 0, 1)]
+
+ refcount_before = sys.getrefcount(request)
+ a.delete_records(request)
+ refcount_after = sys.getrefcount(request)
+
+ assert refcount_after == refcount_before
+
+ # The original bug corrupted the heap rather than crashing immediately,
+ # so a later, unrelated call is what actually segfaulted (list_topics()
+ # in the reported issue).
+ # With no broker configured this call can't succeed, but it must fail cleanly
+ # instead of crashing the interpreter.
+ try:
+ a.list_topics(timeout=0.2)
+ except KafkaException as e:
+ assert e.args[0].code() in (KafkaError._TIMED_OUT, KafkaError._TRANSPORT)
+
+
def test_elect_leaders():
a = AdminClient({"socket.timeout.ms": 10})

Loading