Skip to content

fix(voice): shield transcript rotation from cancellation - #7300

Open
DeepanshuPal wants to merge 1 commit into
livekit:mainfrom
DeepanshuPal:fix/transcript-barrier-cancellation
Open

DeepanshuPal wants to merge 1 commit into
livekit:mainfrom
DeepanshuPal:fix/transcript-barrier-cancellation

Conversation

@DeepanshuPal

Copy link
Copy Markdown

Fixes #7299.

TranscriptSynchronizer.barrier() awaits a session-owned rotation task. Cancelling any barrier waiter currently propagates into that shared task, leaving later barriers unable to complete. Shield the shared await so the caller remains cancellable without cancelling segment rotation.

Added a focused regression that cancels one waiter, verifies the rotation task stays alive, releases it, then confirms a later barrier completes.

Tests:

  • python -m pytest tests/test_transcript_sync_cancellation.py -q
  • ruff check livekit-agents/livekit/agents/voice/transcription/synchronizer.py tests/test_transcript_sync_cancellation.py
  • ruff format --check livekit-agents/livekit/agents/voice/transcription/synchronizer.py tests/test_transcript_sync_cancellation.py

@DeepanshuPal
DeepanshuPal requested a review from a team as a code owner September 16, 2026 05:08
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Devin Review

# just in case, we do log a warning if it does)
while not self._rotate_segment_atask.done():
await self._rotate_segment_atask
await asyncio.shield(self._rotate_segment_atask)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Cancelled shutdown leaks synchronizer tasks

Cancelling aclose() during rotation leaves the shielded task running after shutdown exits. _rotate_segment_task creates a new implementation whose background tasks remain open.

Learn more

aclose() marks the synchronizer closed and then waits at this barrier. Cancellation now exits aclose() without cancelling the rotation. The surviving rotation closes the old implementation, then _rotate_segment_task unconditionally constructs a new implementation with three background tasks. Since the synchronizer is already closed and aclose() has exited, nothing closes that new implementation.

Example: A session starts rotating a transcript segment and then shutdown calls aclose(). If shutdown cancellation arrives before rotation finishes, aclose() exits while rotation continues. Rotation creates a fresh closed-session implementation, leaving its main, capture, and speaking-rate tasks alive.

Recommended fix: Make cancelled shutdown retain ownership of the protected rotation and close whichever implementation it creates. One option is a dedicated close task that awaits rotation and closes the final _impl, with aclose() shielding that task so later cleanup can still await it.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

Cancelling a transcript barrier waiter cancels shared segment rotation and drops subsequent transcripts

2 participants