Skip to content

[NOGIL] Add Free Threading Support - #2347

Draft
Ojasva Jain (ojasvajain) wants to merge 15 commits into
masterfrom
dev_thread_free_support
Draft

[NOGIL] Add Free Threading Support#2347
Ojasva Jain (ojasvajain) wants to merge 15 commits into
masterfrom
dev_thread_free_support

Conversation

@ojasvajain

@ojasvajain Ojasva Jain (ojasvajain) commented Sep 3, 2026

Copy link
Copy Markdown
Member

Free threading support has been under development on dev_thread_free_support branch. This PR merges this branch to master.

PRs merged to feature branch: https://github.com/confluentinc/confluent-kafka-python/pulls?q=is%3Apr+label%3Anogil+is%3Amerged+

Open PRs: https://github.com/confluentinc/confluent-kafka-python/pulls?q=is%3Apr+label%3Anogil+is%3Aopen

Pending: Documentation changes, CHANGELOG update and automating performance test cases.


Checklist

  • Contains customer facing changes? Including API/behavior changes
  • Did you add sufficient unit test and/or integration test coverage for this PR?
    • If not, please explain why it is not required

References

JIRA:

Test & Review

Open questions / Follow-ups

@confluent-cla-assistant

Copy link
Copy Markdown

🎉 All Contributor License Agreements have been signed. Ready to merge.
Please push an empty commit if you would like to re-run the checks to verify CLA status for all contributors.

@airlock-confluentinc
airlock-confluentinc Bot force-pushed the dev_thread_free_support branch 2 times, most recently from 00aa99d to a916f2f Compare September 7, 2026 09:26
* Add CI verification jobs for free-threaded Python 3.14t

  - Add a Semaphore block running source package verification and
    integration tests on CPython 3.14t (classic and consumer group
    protocols). cimpl does not declare free-threading support yet, so
    importing it re-enables the GIL: these jobs validate the 3.14t
    toolchain and packaging until that declaration ships.
  - Skip the CI-only orjson install on free-threaded interpreters: no
    free-threaded orjson wheels exist and the source build would fail;
    the stdlib JSON fallback path stays covered.
  - Add a module-scoped autouse fixture (defined on free-threaded builds
    only) that warns when the GIL is re-enabled around a test module.
    Hard asserts are staged behind TODO FTS markers, to be enabled in the
    same PR that declares Py_MOD_GIL_NOT_USED.

  Interpreter detection follows the free-threading HOWTO:
  https://docs.python.org/3/howto/free-threading-python.html

* Handle deps without free-threaded wheels in test setup

  On free-threaded (no-GIL) builds, the rules and json-fast extras'
  compiled deps (tink, google-re2, grpcio; orjson) ship no free-threaded
  wheels and fail to build from source, so:

  - Add requirements-tests-install-nogil.txt, a variant of
    requirements-tests-install.txt without those extras, and install it
    from source-package-verification.sh when the interpreter is
    free-threaded (detected via Py_GIL_DISABLED).
  - Exclude the schema_registry test modules that import tink/celpy/orjson
    at the top of the file from collection on free-threaded builds only;
    on regular builds a missing dep stays a loud collection error rather
    than a silent skip. Plain serdes coverage recovery is marked as a
    TODO NOGIL follow-up.

* Style fixes
…usions (#2309)

* [NOGIL] Split schema_registry tests to narrow free-threaded test exclusions

  Each of test_avro_serdes.py, test_config.py, test_json_serdes.py, and
  test_proto_serdes.py is split into a plain file (no rules/encryption
  dependency) and a _rules file (CEL/encryption/JSONata-dependent tests).
  This lets the plain tests run on free-threaded (3.14t) builds, where
  tink/celpy/orjson have no free-threaded wheels, while only the _rules
  files stay excluded via conftest.py's collect_ignore.

* Fix isort/black formatting in split schema_registry tests
…stry tests from free-threaded collection

test_azure_aead.py, test_azure_client.py, test_azure_driver.py, and
test_encrypt_executor.py import azure/tink unconditionally at module level
but were missing from conftest.py's free-threaded collect_ignore list,
causing collection errors on 3.14t CI. These tests were added to master
after the original NOGIL schema_registry exclusion list (#2309) landed,
so they weren't accounted for; rebasing onto master surfaced the gap.
…lusions

test_json.py's orjson import became pytest.importorskip upstream, so it no
longer needs excluding. test_dlq_serdes.py is new post-rebase and hard-
imports celpy, so it needs excluding.
…elf (#2313)

* Fix Producer.close() races with concurrent calls and with itself

Producer.close() previously raced with concurrent produce()/poll()/
flush()/produce_batch()/transaction calls and with itself when called
from multiple threads, both leading to use-after-free/double-free on
the underlying rd_kafka_t handle. Adds an active_calls/closing guard
(Handle_enter_rk_use/Handle_exit_rk_use) so every method that touches
self->rk registers itself before use, and close() drains in-flight
calls before tearing down; a CAS on `closing` ensures only one
concurrent close() call performs the actual teardown, with losing
callers waiting for it to finish rather than racing it.

Adds tests/parallel/test_producer_close_race.py covering each affected
method racing close(), close() racing itself, and close()'s blocking
behavior. Uses pytest-forked (POSIX only) so a regression segfault
fails only that test.

Integration tests against a real broker are still pending.

* Replace pytest-forked with subprocess-based test isolation

* Fix subprocess_isolated import and flaky close() timing assertion

* Rename tests/parallel to tests/concurrency and add integration tests for Producer close()/transaction races

* Clarified comment

* Make Producer.close() non-blocking for concurrent callers, add reentrancy tests

Concurrent close() calls now return False immediately with a warning
instead of waiting for the CAS winner, since waiting could deadlock a
caller that already holds an active_calls slot (e.g. a callback invoked
from its own poll()/flush()). poll()/flush() now also exit early once
closing is set instead of blocking the drain-wait.

Fixes an ordering bug in produce_batch() where the topic handle was
destroyed after releasing the active_calls slot. Adds integration tests
for reentrant callbacks and close()'s internal flush delivering all
messages, and documents close()-from-callback as unsupported.

* Fix close() losers to wait for winner instead of returning False early

* Fix CI flakiness in test_close_races_close_losers_wait_for_slow_winner

* Move signal/slow-winner close() race tests to integration suite

* Allow reentrant Producer calls from close()'s own delivery callback

* Update docstring of test case

* Harden race checks
* Add a serializing Consumer reentrancy gate for free-threading support

Introduces gate_owner/gate_depth in Consumer.c so concurrent, cross-caller access to a single Consumer/AIOConsumer instance waits for the current caller to finish rather than being undefined behavior, since librdkafka's consumer is not thread-safe; legitimate re-entrant calls (e.g. a rebalance/commit callback calling back into the Consumer that triggered it) are still admitted immediately. AIOConsumer identity is tracked via a ContextVar since the owning logical caller can move across ThreadPoolExecutor worker threads. Includes unit and integration test coverage for both the sync Consumer and AIOConsumer.

* Style fixes

* Fix flaky tests

* Add TODO for handling concurrent calls inside callback

* Addressed comments

* Trigger CLA check

* Address comments

* Return consistent error when consumer is closed

* Fix one test case

* Fix styling
#2335)

* Exclude Avro tests from running on free threaded python builds

* Exclude only fastavro

* temoporarily install fastavro for docs
)

* Add a serializing Consumer reentrancy gate for free-threading support

Introduces gate_owner/gate_depth in Consumer.c so concurrent, cross-caller access to a single Consumer/AIOConsumer instance waits for the current caller to finish rather than being undefined behavior, since librdkafka's consumer is not thread-safe; legitimate re-entrant calls (e.g. a rebalance/commit callback calling back into the Consumer that triggered it) are still admitted immediately. AIOConsumer identity is tracked via a ContextVar since the owning logical caller can move across ThreadPoolExecutor worker threads. Includes unit and integration test coverage for both the sync Consumer and AIOConsumer.

* Trigger CLA check

* Add Integration test coverage for AIO Producer

* Add Tx related test cases + Add abort_tx call in Producer close

* Fix flaky AIO Producer test case

* Fix for serializing concurrent calls from a Async callback

* Minor improvements to tests

* Fix styling

* Fix flaky test case

* Serialize AIOConsumer calls that outlive their callback invocation instead of admitting them as re-entrant

* Serialize AIOConsumer calls that outlive their callback invocation instead of admitting them as re-entrant

* Revert "Serialize AIOConsumer calls that outlive their callback invocation instead of admitting them as re-entrant"

This reverts commit 42b40cc.

* Revert "Serialize AIOConsumer calls that outlive their callback invocation instead of admitting them as re-entrant"

This reverts commit f75dba3.

* Add TODO
…aces (#2317) (#2346)

* Add a serializing Consumer reentrancy gate for free-threading support

Introduces gate_owner/gate_depth in Consumer.c so concurrent, cross-caller access to a single Consumer/AIOConsumer instance waits for the current caller to finish rather than being undefined behavior, since librdkafka's consumer is not thread-safe; legitimate re-entrant calls (e.g. a rebalance/commit callback calling back into the Consumer that triggered it) are still admitted immediately. AIOConsumer identity is tracked via a ContextVar since the owning logical caller can move across ThreadPoolExecutor worker threads. Includes unit and integration test coverage for both the sync Consumer and AIOConsumer.

* Trigger CLA check

* Add Integration test coverage for AIO Producer

* Add Tx related test cases + Add abort_tx call in Producer close

* Fix styling

* Serialize AIOConsumer calls that outlive their callback invocation instead of admitting them as re-entrant

* Serialize AIOConsumer calls that outlive their callback invocation instead of admitting them as re-entrant

* Trigger CLA check

* Trigger CLA check

* Add a serializing Consumer reentrancy gate for free-threading support

Introduces gate_owner/gate_depth in Consumer.c so concurrent, cross-caller access to a single Consumer/AIOConsumer instance waits for the current caller to finish rather than being undefined behavior, since librdkafka's consumer is not thread-safe; legitimate re-entrant calls (e.g. a rebalance/commit callback calling back into the Consumer that triggered it) are still admitted immediately. AIOConsumer identity is tracked via a ContextVar since the owning logical caller can move across ThreadPoolExecutor worker threads. Includes unit and integration test coverage for both the sync Consumer and AIOConsumer.

* Style fixes

* Addressed comments

* Trigger CLA check

* Address comments

* Add Tx related test cases + Add abort_tx call in Producer close

* Fix for serializing concurrent calls from a Async callback

* Fix one test case

* Fix styling

* [NOGIL] Guard AdminClient against concurrent close()-vs-method-call races

* Add handle check in common APIs and refactor overall handle logic

* Remove conflict marker

* Remove another conflict marker

* Rename handle functions

* Add more tests

* Fix flaky test

* Address comments

* Fix corrupted _common.py

* Fix other corrupted files

* Fix flaky test case
* Add a serializing Consumer reentrancy gate for free-threading support

Introduces gate_owner/gate_depth in Consumer.c so concurrent, cross-caller access to a single Consumer/AIOConsumer instance waits for the current caller to finish rather than being undefined behavior, since librdkafka's consumer is not thread-safe; legitimate re-entrant calls (e.g. a rebalance/commit callback calling back into the Consumer that triggered it) are still admitted immediately. AIOConsumer identity is tracked via a ContextVar since the owning logical caller can move across ThreadPoolExecutor worker threads. Includes unit and integration test coverage for both the sync Consumer and AIOConsumer.

* Trigger CLA check

* Add Integration test coverage for AIO Producer

* Add Tx related test cases + Add abort_tx call in Producer close

* Fix styling

* Serialize AIOConsumer calls that outlive their callback invocation instead of admitting them as re-entrant

* Serialize AIOConsumer calls that outlive their callback invocation instead of admitting them as re-entrant

* Trigger CLA check

* Trigger CLA check

* Add a serializing Consumer reentrancy gate for free-threading support

Introduces gate_owner/gate_depth in Consumer.c so concurrent, cross-caller access to a single Consumer/AIOConsumer instance waits for the current caller to finish rather than being undefined behavior, since librdkafka's consumer is not thread-safe; legitimate re-entrant calls (e.g. a rebalance/commit callback calling back into the Consumer that triggered it) are still admitted immediately. AIOConsumer identity is tracked via a ContextVar since the owning logical caller can move across ThreadPoolExecutor worker threads. Includes unit and integration test coverage for both the sync Consumer and AIOConsumer.

* Style fixes

* Addressed comments

* Trigger CLA check

* Address comments

* Add Tx related test cases + Add abort_tx call in Producer close

* Fix for serializing concurrent calls from a Async callback

* Fix one test case

* Fix styling

* [NOGIL] Guard AdminClient against concurrent close()-vs-method-call races

* Add handle check in common APIs and refactor overall handle logic

* Remove conflict marker

* Remove another conflict marker

* Rename handle functions

* Add more tests

* Fix flaky test

* Address comments

* Fix corrupted _common.py

* Fix other corrupted files

* Fix flaky test case

* [NOGIL] Fix borrowed-reference/stale-count races in Admin/Producer/Consumer parse loop

* Fix list mutation in Admin API and a flaky test case

* Fix data race on Handle.rk in close()/__exit__()/__len__() by making liveness checks atomic

* Fix flaky test

* Address comments
@airlock-confluentinc
airlock-confluentinc Bot force-pushed the dev_thread_free_support branch from a916f2f to 24155f9 Compare September 8, 2026 06:57
@sonarqube-confluent

Copy link
Copy Markdown

Quality Gate failed Quality Gate failed

Failed conditions
6.1% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube

@ojasvajain Ojasva Jain (ojasvajain) changed the title [WIP][NOGIL] Add free threading support [NOGIL] Add Free Threading Support Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant