Expected Behaviour
A POST request to an HttpResolverLocal route whose body fails pydantic validation (when enable_validation=True) should return HTTP 422 with the standard validation-error response.
Current Behaviour
The request hangs forever. The connection is accepted, the request body is received, but the response is never sent and no log line is emitted. Concurrent requests on the same uvicorn worker are unaffected, but the worker accumulates hung tasks (visible during shutdown as Waiting for background tasks to complete).
Code snippet
Minimal standalone repro — no custom exception handlers, no extra middleware:
# repro_app.py
from aws_lambda_powertools.event_handler import HttpResolverLocal
from pydantic import BaseModel, Field
class Body(BaseModel):
items: list[str] = Field(min_length=1)
app = HttpResolverLocal(enable_validation=True)
@app.get("/ping")
def ping() -> dict[str, str]:
return {"status": "ok"}
@app.post("/echo")
def echo(body: Body) -> dict[str, list[str]]:
return {"items": body.items}
Run it:
uvicorn repro_app:app --host 0.0.0.0 --port 8090
Then:
# Works:
curl -s --max-time 5 http://localhost:8090/ping # → 200
curl -s --max-time 5 -X POST http://localhost:8090/echo \
-H 'content-type: application/json' -d '{"items":["a"]}' # → 200
# Hangs forever:
curl -s --max-time 5 -X POST http://localhost:8090/echo \
-H 'content-type: application/json' -d '{"items":[]}' # → timeout
Possible Solution
Root cause is in aws_lambda_powertools.event_handler.http_resolver._wrap_middleware_async.
The async coroutine awaits middleware_called_next.wait() to coordinate with the sync middleware running in a thread. When the sync middleware (e.g. OpenAPIValidationMiddleware) raises before calling sync_next — which is exactly what happens on RequestValidationError — the thread captures the exception in middleware_error_holder and exits. But the event is never set, so the awaiting coroutine waits forever.
Suggested fix — set the event in a finally block on run_middleware, and after await middleware_called_next.wait(), check whether next_app_holder is empty (i.e. the middleware errored without calling next) and propagate the captured exception immediately:
def run_middleware() -> None:
try:
middleware_result_holder.append(middleware(app, sync_next))
except Exception as e:
middleware_error_holder.append(e)
finally:
loop.call_soon_threadsafe(middleware_called_next.set)
# ...
await middleware_called_next.wait()
if middleware_error_holder and not next_app_holder:
thread.join()
raise middleware_error_holder[0]
I confirmed this fix locally via monkey-patch — the empty-list request then returns HTTP 422 correctly through the framework's built-in validation-error path.
Steps to Reproduce
- Save the snippet above to
repro_app.py.
pip install "aws-lambda-powertools[all]==3.28.0" pydantic uvicorn
uvicorn repro_app:app --host 0.0.0.0 --port 8090
curl --max-time 5 -X POST http://localhost:8090/echo -H 'content-type: application/json' -d '{"items":[]}' — observe the 5s timeout (HTTP 000).
The lambda invocation path (app.resolve(event, context)) is unaffected and returns 422 correctly — the deadlock is specific to the ASGI __call__ path used by uvicorn.
Powertools for AWS Lambda (Python) version
3.28.0 (also reproduced on 3.27.0)
AWS Lambda function runtime
N/A (local dev — issue is in HttpResolverLocal ASGI path, not Lambda)
Debugging logs
No application logs are emitted for the hung request — the deadlock occurs before the route handler runs. uvicorn logs only show the request never completing; on shutdown:
INFO: Shutting down
INFO: Waiting for background tasks to complete. (CTRL+C to force quit)
(Container/process must be force-killed to recover.)
Expected Behaviour
A
POSTrequest to anHttpResolverLocalroute whose body fails pydantic validation (whenenable_validation=True) should returnHTTP 422with the standard validation-error response.Current Behaviour
The request hangs forever. The connection is accepted, the request body is received, but the response is never sent and no log line is emitted. Concurrent requests on the same uvicorn worker are unaffected, but the worker accumulates hung tasks (visible during shutdown as
Waiting for background tasks to complete).Code snippet
Minimal standalone repro — no custom exception handlers, no extra middleware:
Run it:
Then:
Possible Solution
Root cause is in
aws_lambda_powertools.event_handler.http_resolver._wrap_middleware_async.The async coroutine awaits
middleware_called_next.wait()to coordinate with the sync middleware running in a thread. When the sync middleware (e.g.OpenAPIValidationMiddleware) raises before callingsync_next— which is exactly what happens onRequestValidationError— the thread captures the exception inmiddleware_error_holderand exits. But the event is never set, so the awaiting coroutine waits forever.Suggested fix — set the event in a
finallyblock onrun_middleware, and afterawait middleware_called_next.wait(), check whethernext_app_holderis empty (i.e. the middleware errored without callingnext) and propagate the captured exception immediately:I confirmed this fix locally via monkey-patch — the empty-list request then returns
HTTP 422correctly through the framework's built-in validation-error path.Steps to Reproduce
repro_app.py.pip install "aws-lambda-powertools[all]==3.28.0" pydantic uvicornuvicorn repro_app:app --host 0.0.0.0 --port 8090curl --max-time 5 -X POST http://localhost:8090/echo -H 'content-type: application/json' -d '{"items":[]}'— observe the 5s timeout (HTTP 000).The lambda invocation path (
app.resolve(event, context)) is unaffected and returns 422 correctly — the deadlock is specific to the ASGI__call__path used by uvicorn.Powertools for AWS Lambda (Python) version
3.28.0 (also reproduced on 3.27.0)
AWS Lambda function runtime
N/A (local dev — issue is in
HttpResolverLocalASGI path, not Lambda)Debugging logs
No application logs are emitted for the hung request — the deadlock occurs before the route handler runs. uvicorn logs only show the request never completing; on shutdown:
(Container/process must be force-killed to recover.)