Skip to content

fix(stt): clean up input frame reads#1869

Open
rosetta-livekit-bot[bot] wants to merge 1 commit into
1.5.0from
port-stt-input-cleanup
Open

fix(stt): clean up input frame reads#1869
rosetta-livekit-bot[bot] wants to merge 1 commit into
1.5.0from
port-stt-input-cleanup

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Port the upstream STT input-frame cleanup from fix(google): clean up STT input frame task agents#6193 to agents-js equivalents.
  • Make AsyncIterableQueue.next() abort-aware so canceled STT send loops do not leave pending input reads behind.
  • Update streaming STT providers to await abort-aware input reads directly instead of racing input.next() against abort promises.

Notes

  • agents-js does not currently have a Google STT plugin, so this applies the same cleanup pattern to the shared STT input path and provider STT send loops.
  • No tests added, matching the request not to add tests beyond the original production port.

Verification

  • pnpm --filter @livekit/agents --filter @livekit/agents-plugins-test --filter @livekit/agents-plugin-silero --filter @livekit/agents-plugin-openai --filter @livekit/agents-plugin-assemblyai --filter @livekit/agents-plugin-cartesia --filter @livekit/agents-plugin-deepgram --filter @livekit/agents-plugin-elevenlabs --filter @livekit/agents-plugin-inworld --filter @livekit/agents-plugin-sarvam --filter @livekit/agents-plugin-soniox --filter @livekit/agents-plugin-xai build

Ported from livekit/agents#6193

Original PR description

Summary

Fixes #6181.

This cleans up the Google STT streaming request generator when the surrounding gRPC stream is cancelled while it is waiting for the next audio frame. The user-visible streaming behavior is unchanged, but the pending self._input_ch.recv() task is now cancelled and awaited before the generator exits.

Background

PR #5591 added a race between self._input_ch.recv() and a stop event so Google STT can reconnect without leaving an idle request generator parked on the input channel. That fixed the idle-stream leak, but left a smaller cleanup gap: if the request generator itself is cancelled while frame_task is still waiting on Chan.recv(), the generator's finally block only cancelled stop_task.

When the input channel later closes, that orphaned task can finish with ChanClosed, causing:

Task exception was never retrieved
future: <Task finished ... coro=<Chan.recv()> exception=ChanClosed()>

Changes

  • Track the active Google STT input frame_task.
  • Cancel and await both stop_task and any pending frame_task via utils.aio.gracefully_cancel.
  • Add a regression test covering stream cancellation while waiting for audio.
  • Mock Google ADC only for config-only tests so test_plugin_google_stt.py can run locally without real credentials.

Validation

  • python -m pytest tests/test_plugin_google_stt.py -q
    • 16 passed
  • python -m ruff format --check livekit-plugins/livekit-plugins-google/livekit/plugins/google/stt.py tests/test_plugin_google_stt.py
  • python -m ruff check livekit-plugins/livekit-plugins-google/livekit/plugins/google/stt.py tests/test_plugin_google_stt.py
  • python -m py_compile livekit-plugins/livekit-plugins-google/livekit/plugins/google/stt.py tests/test_plugin_google_stt.py
  • git diff --check

@changeset-bot

changeset-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 90853e0

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@rosetta-livekit-bot rosetta-livekit-bot Bot requested a review from longcw June 24, 2026 02:56

@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.

Open in Devin Review

Comment thread agents/src/utils.ts
Comment on lines +1414 to +1440
export function combineAbortSignals(signals: AbortSignal[]): AbortSignal {
const controller = new AbortController();
const cleanupCallbacks: (() => void)[] = [];

const abort = () => {
for (const cleanup of cleanupCallbacks) {
cleanup();
}
cleanupCallbacks.length = 0;

if (!controller.signal.aborted) {
controller.abort();
}
};

for (const signal of signals) {
if (signal.aborted) {
abort();
break;
}

signal.addEventListener('abort', abort, { once: true });
cleanupCallbacks.push(() => signal.removeEventListener('abort', abort));
}

return controller.signal;
}

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.

🚩 combineAbortSignals duplicates existing combineSignals with different API

The new combineAbortSignals(signals: AbortSignal[]) at agents/src/utils.ts:1414 overlaps significantly with the existing combineSignals(a, b) at agents/src/utils.ts:1455. Key differences: (1) the new function takes an array vs two params, (2) the new function cleans up listeners on other signals when one fires, (3) the new function does NOT propagate the abort reason (controller.abort() with no argument), while the old one does (c.abort((s as any).reason)). Both leak listeners if neither signal ever fires, which is acceptable in practice since signals are eventually aborted. Consider consolidating these in a future PR.

Open in 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.

0 participants