Skip to content

fix(rtc): use inspect.iscoroutinefunction in EventEmitter.on - #814

Closed
xbt-a4224j wants to merge 1 commit into
livekit:mainfrom
xbt-a4224j:fix/event-emitter-iscoroutinefunction
Closed

xbt-a4224j wants to merge 1 commit into
livekit:mainfrom
xbt-a4224j:fix/event-emitter-iscoroutinefunction

Conversation

@xbt-a4224j

Copy link
Copy Markdown
Contributor

Fixes #790

Problem

EventEmitter.on() calls asyncio.iscoroutinefunction() to reject coroutine callbacks. That function is deprecated in Python 3.14 and slated for removal in 3.16 (CPython gh-122858), so on 3.14 every callback registration emits a DeprecationWarning:

livekit/rtc/event_emitter.py:160: DeprecationWarning: 'asyncio.iscoroutinefunction' is
deprecated and slated for removal in Python 3.16; use inspect.iscoroutinefunction() instead

Because Room and friends register many handlers, this is noisy in practice — the existing tests/rtc/test_emitter.py alone produced 10 of them. It also becomes a hard failure in 3.16.

Fix

Use inspect.iscoroutinefunction(), which the module already imports.

The two are equivalent for every input this guard sees. They originally differed only because asyncio's variant also recognised generator-based coroutines marked by @asyncio.coroutine; that decorator was removed in Python 3.11, leaving nothing for the marker to mark. I verified equivalence rather than assuming it — async def, plain def, lambda, sync/async __call__ objects, functools.partial (single and nested), async generator functions, and inspect.markcoroutinefunction all agree on Python 3.14.7.

Compatibility is safe for this package: inspect.iscoroutinefunction has existed since 3.5 and has unwrapped functools.partial since 3.8, and requires-python here is >=3.9.

import asyncio is removed because it is now unused — the only remaining occurrence of "asyncio" in the file is inside the ValueError message text, not a reference. Leaving it would fail ruff F401.

Testing

Two tests added to tests/rtc/test_emitter.py:

  • test_on_does_not_warn — promotes DeprecationWarning to an error around registration, so a future reintroduction fails loudly.
  • test_on_rejects_async_callback — asserts the guard still rejects coroutine functions, including a functools.partial-wrapped one. The guard previously had no test coverage.

Verified as a real flip against the released livekit wheel on Python 3.14.7:

  • unpatched: 1 failed, 5 passed, 12 warnings
  • patched: 6 passed, 2 warnings
  • restoring the unpatched file returns 1 failed, 5 passed

Lint, run as CI runs it from the repo root — both clean, and unchanged from main:

  • ruff check . — All checks passed!
  • ruff format --check . — 101 files already formatted

Note: I could not run the full livekit-rtc/tests suite locally, as those are end-to-end tests requiring LIVEKIT_URL / LIVEKIT_API_KEY / LIVEKIT_API_SECRET, and Git LFS was unavailable so the audio fixtures remained pointer files. The change is confined to a pure-Python guard with no FFI surface.

@CLAassistant

CLAassistant commented Sep 14, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@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

"""
if callback is not None:
if asyncio.iscoroutinefunction(callback):
if inspect.iscoroutinefunction(callback):

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.

🟡 Legacy coroutine callbacks are accepted

On Python 3.9 and 3.10, inspect.iscoroutinefunction accepts callbacks decorated with asyncio.coroutine. emit discards their returned generators, so listeners never execute.

Learn more

The package supports Python 3.9 and 3.10, where asyncio.coroutine still creates generator-based coroutine functions. asyncio.iscoroutinefunction recognizes its _is_coroutine marker, while inspect.iscoroutinefunction does not. The new guard therefore changes behavior on supported runtimes. Registration succeeds, and emit invokes the callback synchronously without awaiting or driving the returned generator.

Example: On Python 3.10, a listener defined with @asyncio.coroutine previously raises ValueError during on. After this change, registration succeeds, but emitting the event never runs the listener body.

Recommended fix: Preserve _is_coroutine marker recognition without calling the deprecated API on Python 3.14. Add a regression test using @asyncio.coroutine on runtimes that provide it, or an equivalently marked callable.

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.

Python 3.14: EventEmitter uses deprecated asyncio.iscoroutinefunction

2 participants