Skip to content

fix(rtc): reject async callbacks in EventEmitter.once - #903

Open
NabiBukhsh-AI wants to merge 1 commit into
livekit:mainfrom
NabiBukhsh-AI:fix/rtc-emitter-once-reject-async
Open

NabiBukhsh-AI wants to merge 1 commit into
livekit:mainfrom
NabiBukhsh-AI:fix/rtc-emitter-once-reject-async

Conversation

@NabiBukhsh-AI

Copy link
Copy Markdown
Contributor

Summary

EventEmitter.on() rejects coroutine functions, functools.partial(async_fn), and unittest.mock.AsyncMock by raising:

ValueError("Cannot register an async callback with `.on()`. Use `asyncio.create_task` within your synchronous callback instead.")

However, EventEmitter.once() wrapped the provided callback in a synchronous wrapper (once_callback) and passed it to self.on(), bypassing the check entirely.

When an async callback was registered via emitter.once(event, async_fn) or @emitter.once(event):

  1. Registration succeeded silently.
  2. On emit(), once_callback invoked the async callback and received a coroutine object.
  3. Because once_callback is synchronous, the returned coroutine was never awaited and was dropped with a Python RuntimeWarning: coroutine '...' was never awaited.
  4. The user's callback logic silently never ran.

Changes

  • Extracted _check_async_callback(callback, method_name) to validate callbacks consistently across both on() and once().
  • Both methods now reject coroutine functions, partials wrapping coroutine functions, sentinel-tagged coroutines (_ASYNCIO_COROUTINE_MARKER), and AsyncMock with a descriptive ValueError.

Testing

Added unit tests in tests/rtc/test_emitter.py:

  • test_once_rejects_async_callback: rejects direct async callbacks, functools.partial(async_fn), and @emitter.once decorated async functions.

  • test_once_rejects_sentinel_tagged_callback: rejects callbacks tagged with _ASYNCIO_COROUTINE_MARKER.

  • test_once_rejects_async_mock: rejects unittest.mock.AsyncMock.

  • uv run pytest tests/rtc/test_emitter.py -v -W error (15/15 passed)

  • uv run ruff check livekit-rtc/livekit/rtc/event_emitter.py tests/rtc/test_emitter.py (passed)

  • uv run ruff format --check livekit-rtc/livekit/rtc/event_emitter.py tests/rtc/test_emitter.py (passed)

  • uv run mypy livekit-rtc/livekit/rtc/event_emitter.py tests/rtc/test_emitter.py (0 issues)

@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: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

This branch has not been deployed

No deployments
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.

1 participant