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
- Expose an `app = web.Application(...)` module-level instance, or an `async def app_factory(): return web.Application(...)`.
- Ensure the factory is `async def` (the worker awaits it); a plain `def` returning Application is not accepted.
- Double-check the gunicorn module spec points to the module containing the app/factory, not a sub-attribute that is not an Application.
- 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
- Expose `app = web.Application(...)` or `async def app()` at module level for gunicorn.
- Ensure factories are async def; sync factories are rejected.
- Validate the app object type in tests before deploying with gunicorn.
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
- Gunicorn's style options in form of `%(name)s` are not suppo
- SSL is not supported.
- keepalive_timeout cannot be set if force_close is True
- ssl should be SSLContext, Fingerprint, or bool, got {ssl!r}
- No .netrc file found
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/c49298eb45578a92.json.
Report an issue: GitHub.