Fix two crashes in the aiokafka threaded producer - #759
Open
wbarnha wants to merge 1 commit into
Open
Conversation
Both were found by the type checker in #758 and left marked `XXX` there because fixing them changes runtime behaviour. `ThreadedProducer._shutdown_thread` was a plain `def` overriding `mode.threads.ServiceThread._shutdown_thread`, which is `async def` and is awaited by `_serve()` in a `finally:`. Every shutdown of the producer thread therefore evaluated `await None` and raised TypeError. The thread only recovered because `_start_thread` catches that exception and calls `set_shutdown()` before re-raising -- so mode's teardown (`on_thread_stop`, stopping children, futures and exit stacks) never ran, and the thread died with a traceback instead of stopping cleanly. The override also scheduled `on_thread_stop()` with `asyncio.run_coroutine_threadsafe` onto `self.thread_loop` -- the loop that was about to stop, and the loop already running `_serve()`. Because the TypeError tore down `run_until_complete` immediately, that coroutine never got a chance to run, so the producer was never flushed or stopped on this path. Make it `async def` and await `super()._shutdown_thread()`, which runs `on_thread_stop()` and the rest of mode's teardown in order. The once-only guard is kept; when shutdown has already been initiated the shutdown event is still set, matching what the old TypeError path ended up doing via `_start_thread`. `ThreadedProducer.publish_message(wait=True)` called `fut.message.channel._on_published(message=..., state=..., producer=...)`. `Topic._on_published` takes the send future as a required *positional* `fut` and reads the result off it, so the call raised `TypeError: Topic._on_published() missing 1 required positional argument`. The waiting branch has no such future -- `send_and_wait` has already resolved -- so complete the message directly instead: report the sensor, set the result, and invoke the callback, which is what `Topic.publish_message(wait=True)` does via `_finalize_message`. The non-waiting branch keeps using `_on_published` as a done-callback, where `add_done_callback` supplies `fut`. `test_publish_message_with_wait` did not catch this because its channel is a bare `Mock`, which accepts any call; the new test uses a real topic and fails with the TypeError above against the previous code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012N2yysiNbzzVcvYhtrpsM7
wbarnha
force-pushed
the
claude/faust-aiokafka-shutdown-publish-fixes
branch
from
August 6, 2026 20:08
805596d to
02a772c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Both bugs were surfaced by the type checker in #758 and left marked
XXXthere, because fixing them changes runtime behaviour and that PR was annotation-only. This PR fixes them, each with a regression test.1.
_shutdown_threadraised TypeError on every producer-thread shutdownThreadedProducer._shutdown_threadwas a plaindefoverridingmode.threads.ServiceThread._shutdown_thread, which isasync defand is awaited by_serve():So every shutdown of the thread evaluated
await Noneand raisedTypeError. The thread only appeared to recover because_start_threadcatches the exception and callsset_shutdown()before re-raising — meaning mode's teardown (on_thread_stop, stopping children, futures and exit stacks) never ran, and the thread died with a traceback rather than stopping cleanly.There was a second layer to it. The override scheduled
on_thread_stop()with:thread_loopis both the loop already running_serve()and the loop about to stop. Because theTypeErrortore downrun_until_completeimmediately, that coroutine never got a chance to run — so on this path the producer was never flushed and never stopped.The fix makes it
async defand awaitssuper()._shutdown_thread(), which runson_thread_stop()and the rest of mode's teardown in the right order. The once-only guard is preserved, and when shutdown has already been initiated the shutdown event is still set — matching what the old TypeError path ended up doing via_start_thread, sostop()cannot hang.2.
publish_message(wait=True)could never succeedThe waiting branch called:
Topic._on_publishedtakes the send future as a required positional parameter and reads the result off it:Nothing was passed for
fut, so this raisedTypeError: Topic._on_published() missing 1 required positional argument: 'fut'for any real channel._on_publishedis the done-callback for the non-waiting branch, whereadd_done_callbacksupplies the future positionally. The waiting branch has no such future —send_and_waithas already resolved toret— so it now completes the message directly: report the sensor, set the result, invoke the callback. That is exactly whatTopic.publish_message(wait=True)does via_finalize_message. The non-waiting branch is unchanged.Why the existing test missed it
test_publish_message_with_waitpasses today because its channel is a bareMock(), which accepts any call and swallows the missing argument — it pinned the bug rather than catching it. The new test uses a real topic; against the previous code it fails with theTypeErrorabove.Tests
Four tests added, all verified to fail against the pre-fix code:
test_publish_message_with_wait__completes_the_message— real channel; asserts the future resolves to thesend_and_waitresult, the message callback fires, andon_send_completedis reported.test_shutdown_thread_is_a_coroutine— the structural guarantee mode'sawaitdepends on.test_shutdown_thread__runs_mode_teardown— delegates to the base implementation.test_shutdown_thread__already_initiated_still_sets_shutdown— no doubleon_thread_stop, but the shutdown event is still set.scripts/checkpasses with the pinned toolchain; suites go from 2207 to 2211 passed, 4 skipped.🤖 Generated with Claude Code