aio-libs/aiohttp · critical · RuntimeError

wsgi app should be either Application or async function retu

Error message

wsgi app should be either Application or async function returning Application, got {self.wsgi}

What it means

Raised by GunicornWebWorker._run() when the `wsgi` (the app entry configured for the gunicorn worker) is neither an aiohttp.web.Application instance nor an async callable that returns an Application (or AppRunner). The worker needs a concrete Application to wrap in an AppRunner and bind to sockets, so anything else is rejected at startup.

Source

Thrown at aiohttp/worker.py:83

            self.loop.close()

        sys.exit(self.exit_code)

    async def _run(self) -> None:
        runner = None
        if isinstance(self.wsgi, Application):
            app = self.wsgi
        elif inspect.iscoroutinefunction(self.wsgi) or (
            sys.version_info < (3, 14) and asyncio.iscoroutinefunction(self.wsgi)  # type: ignore[deprecated]
        ):
            wsgi = await self.wsgi()
            if isinstance(wsgi, web.AppRunner):
                runner = wsgi
                app = runner.app
            else:
                app = wsgi
        else:
            raise RuntimeError(
                "wsgi app should be either Application or "
                f"async function returning Application, got {self.wsgi}"
            )

        if runner is None:
            access_log = self.log.access_log if self.cfg.accesslog else None
            runner = web.AppRunner(
                app,
                logger=self.log,
                keepalive_timeout=self.cfg.keepalive,
                access_log=access_log,
                access_log_format=self._get_valid_log_format(
                    self.cfg.access_log_format
                ),
                shutdown_timeout=self.cfg.graceful_timeout / 100 * 95,
            )
        await runner.setup()

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Expose an `app = web.Application(...)` module-level instance, or an `async def app_factory(): return web.Application(...)`.
  2. Ensure the factory is `async def` (the worker awaits it); a plain `def` returning Application is not accepted.
  3. Double-check the gunicorn module spec points to the module containing the app/factory, not a sub-attribute that is not an Application.
  4. If returning an AppRunner from the factory, return it directly (the worker detects web.AppRunner).

Example fix

# before (module passed to gunicorn)
def app():
    return web.Application()  # sync, not accepted

# after
app = web.Application()
# or
async def app():
    return web.Application()
Defensive patterns

Strategy: type-guard

Validate before calling

import inspect
from aiohttp import web

def validate_app(app):
    if isinstance(app, web.Application):
        return app
    if inspect.iscoroutinefunction(app):
        return app  # async factory
    raise TypeError("app must be Application or async factory")

Type guard

import inspect
from aiohttp import web

def is_valid_app_entry(obj) -> bool:
    return isinstance(obj, web.Application) or inspect.iscoroutinefunction(obj)

Try / catch

try:
        worker._run()
except RuntimeError as e:
    if "wsgi app should be" in str(e):
        # fix the configured app module to expose Application/async factory
        raise
    raise

Prevention

When it happens

Trigger: Configuring gunicorn with a module path that resolves to a plain function, a sync function, a class, None, or a string, instead of an Application or async factory. The else branch at aiohttp/worker.py:82-86 fires after the isinstance and iscoroutinefunction checks fail.

Common situations: Pointing gunicorn `-w`/`--module` at a module whose only callable is a sync factory; an app factory that returns an AppRunner is fine but one returning a coroutine-of-something-else is not; passing the Application class instead of an instance; a typo in the gunicorn app module spec.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/c49298eb45578a92.json. Report an issue: GitHub.