Setup: gunicorn with -k uvicorn.workers.UvicornWorker --max-requests 5000 --max-requests-jitter 500 (FastAPI on Render).
Trap: gunicorn's own max_requests path (sync/gthread workers) logs Autorestarting worker after current request. UvicornWorker passes the limit to uvicorn as limit_max_requests, and uvicorn enforces it itself, logging:
Maximum request limit of 5449 exceeded. Terminating process.
Shutting down
...
Worker exiting (pid: N)
Booting worker with pid: M (~3s later, same instance)N is the jittered limit. A grep for Autorestarting returns 0, so worker respawns that leave no app-level recycle event look like unattributed restarts (possible OOM/timeout) when they are routine backstop churn.
Classify respawns by pairing each Booting worker with a Maximum request limit line on the same instance within a few seconds. In our case 54/54 paired, with 0 WORKER TIMEOUT, 0 SIGKILL, 0 Starting gunicorn. Anything unpaired is the real anomaly. Note that a bare Booting worker with no preceding exit/recycle/signal line is still the hard-kill (OOM) signature.
Corollary: if you emit telemetry from an app-level RSS recycler, it goes silent once workers stop reaching the RSS limit, and the max-requests path emits nothing. A falling recycle count then says nothing about churn; count the uvicorn line instead.