Skip to content

queue: honour poll timeout between handled internal ops (#5325) - #5538

Open
Tamir Suliman (allamiro) wants to merge 3 commits into
confluentinc:masterfrom
allamiro:fix-5325-poll-q-pop-serve-timeout
Open

queue: honour poll timeout between handled internal ops (#5325)#5538
Tamir Suliman (allamiro) wants to merge 3 commits into
confluentinc:masterfrom
allamiro:fix-5325-poll-q-pop-serve-timeout

Conversation

@allamiro

Copy link
Copy Markdown

Fixes #5325.

Problem

rd_kafka_poll(0) / rd_kafka_consumer_poll(0) (and other queue poll variants) can block far longer than the requested timeout — up to indefinitely — when internal ops are enqueued faster than the application thread drains them. The reported trigger is debug=all + log.queue=true (which routes log messages as RD_KAFKA_OP_LOG onto the polled queue), where broker threads flood the queue with log ops. The issue reporter observed consumer_poll(0) blocking for over 2 hours in production.

Root cause

In rd_kafka_q_pop_serve0() the timeout is only evaluated once the queue is found empty (at the cnd_timedwait_abs() path). After handling an internal op (RD_KAFKA_OP_RES_HANDLED / RD_KAFKA_OP_RES_KEEP, e.g. a log op) the loop does goto retry without re-checking the timeout. If internal ops keep arriving the queue never empties, so the timeout path is never reached and the call is starved.

Fix

Re-check the timeout after each handled op, before jumping back to retry. At least one op is always handled per call, so forward progress is guaranteed, while control is returned to the application once the timeout has expired.

This matches the fix proposed and verified in the issue, with one addition: the early return also calls rd_kafka_app_polled() when the queue can contain fetched messages, consistent with every other return path after rd_kafka_app_poll_start(). Omitting it (as in the issue snippet) would leave the max.poll.interval.ms bookkeeping unbalanced.

Behaviour note for reviewers

For an already-expired timeout (i.e. poll(0)), the call now handles one internal op and returns, rather than draining all currently-queued internal ops in a single call. This is bounded and FIFO-safe (a returnable message behind buffered internal ops surfaces on a subsequent call; it is never lost or reordered), and is the same semantics as the verified fix in the issue. Polls with a non-zero timeout drain until the timeout elapses, as before.

Test

Adds a deterministic regression unit test q_pop_serve_starvation (src/rdkafka_queue.c, registered in src/rdunittest.c) that continuously feeds the queue during an RD_POLL_NOWAIT poll and asserts the poll honours its timeout (handles exactly one op).

  • Fails on unpatched code (drains all fed ops in a single NOWAIT poll).
  • Passes with the fix.
  • Full unit-test suite (25 tests) passes.
  • make style-check-changed clean (clang-format 18).

A CHANGELOG.md entry is included under Fixes.

…#5325)

rd_kafka_q_pop_serve() only evaluated the timeout once the queue was
found empty. After handling an internal op (RD_KAFKA_OP_RES_HANDLED or
RD_KAFKA_OP_RES_KEEP, e.g. a log op) it jumped straight back to the
`retry` label without re-checking the timeout.

If internal ops are enqueued faster than the application thread drains
them - for instance high-volume log ops with log.queue=true and debug
logging enabled - the queue never empties, the timeout path is never
reached, and poll()/consumer_poll() can block far longer than the
requested timeout, up to indefinitely. A poll(0) intended to be
non-blocking was observed blocking for over two hours in production.

Re-check the timeout after each handled op so control is always
returned to the application once the timeout has expired, while still
guaranteeing forward progress of at least one op per call.

Adds a deterministic regression unit test (q_pop_serve_starvation)
that feeds the queue continuously during a NOWAIT poll and asserts the
poll returns after a single handled op.
@allamiro
Tamir Suliman (allamiro) requested a review from a team as a code owner June 29, 2026 16:13
@confluent-cla-assistant

Copy link
Copy Markdown

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

poll(0) blocks indefinitely when log.queue=true and debug is enabled — goto retry in rd_kafka_q_pop_serve() skips timeout check

1 participant