You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
First authenticated request after MySQL idle disconnect returns 500: RevokedToken.is_revoked runs on a stale session before FAB's recovery (#62919) can kick in #71395
With MySQL as the metadata DB and the FAB auth manager, after the api-server sits idle longer than MySQL's wait_timeout, the first authenticated request (any UI or REST endpoint) fails with HTTP 500. The exception is an OperationalError (MySQL error 4031, "The client was disconnected by the server because of inactivity") raised from RevokedToken.is_revoked inside BaseAuthManager.get_user_from_token:
The immediately following request succeeds, because the failed query poisons and effectively resets the pooled connection. In production this shows up as intermittent 500s on the first request after overnight idle periods.
Traceback (trimmed)
File ".../airflow/api_fastapi/auth/managers/base_auth_manager.py", line 152, in get_user_from_token
if (jti := payload.get("jti")) and RevokedToken.is_revoked(jti):
File ".../airflow/utils/session.py", line 100, in wrapper
return func(*args, session=session, **kwargs)
File ".../airflow/models/revoked_token.py", line 61, in is_revoked
return bool(session.scalar(select(exists().where(cls.jti == jti))))
...
sqlalchemy.exc.OperationalError: (MySQLdb.OperationalError) (4031,
'The client was disconnected by the server because of inactivity.
See wait_timeout and interactive_timeout for configuring this behavior.')
How to reproduce
Run Airflow 3.2.x with MySQL as the metadata DB and the FAB auth manager.
Lower the idle timeout to make the repro fast: SET GLOBAL wait_timeout = 60;
Make any authenticated API request (this warms up the pooled connection).
Wait longer than wait_timeout (e.g. 90 s) without touching the api-server.
Repeat the same request → HTTP 500 with OperationalError 4031 raised from RevokedToken.is_revoked. Repeat once more → succeeds.
What you think should happen instead?
Root cause
The FAB auth manager shares core's scoped settings.Session. FabAuthManager.deserialize_user leaves a transaction open after the request is served, so the connection stays checked out across requests. Once it idles past MySQL's wait_timeout, the server drops it (error 4031). Because the connection is never returned to the pool, there is no checkout event — pool_pre_ping / pool_recycle cannot help.
The first request after an idle disconnect should recover transparently, as it does in deserialize_user since #62919. The revoked-token check should apply the same recovery: on SQLAlchemyError, discard the scoped session (settings.Session.remove()) and retry once on a fresh connection. Something like:
ifjti:=payload.get("jti"):
try:
revoked=RevokedToken.is_revoked(jti)
exceptSQLAlchemyError:
log.warning(
"Revoked-token check failed on a stale DB session; ""discarding the session and retrying once.",
exc_info=True,
)
withsuppress(Exception):
settings.Session.remove()
revoked=RevokedToken.is_revoked(jti)
ifrevoked:
raiseInvalidTokenError("Token has been revoked")
Alternatively, the recovery could be hoisted so it covers the whole get_user_from_token path instead of being duplicated per call site.
Operating System
No response
Deployment
None
Apache Airflow Provider(s)
No response
Versions of Apache Airflow Providers
apache-airflow-providers-fab==3.6.4 (already includes the #62919 fix — it is bypassed, not missing)
Official Helm Chart version
1.22.0 (latest released)
Kubernetes Version
1.25
Helm Chart configuration
No response
Docker Image customizations
No response
Anything else?
Occurs deterministically whenever the first request after an idle period touches a connection older than wait_timeout; looks intermittent in production.
Under which category would you file this issue?
Providers
Apache Airflow version
3.2.2
What happened and how to reproduce it?
With MySQL as the metadata DB and the FAB auth manager, after the api-server sits idle longer than MySQL's
wait_timeout, the first authenticated request (any UI or REST endpoint) fails with HTTP 500. The exception is anOperationalError(MySQL error 4031, "The client was disconnected by the server because of inactivity") raised fromRevokedToken.is_revokedinsideBaseAuthManager.get_user_from_token:https://github.com/apache/airflow/blob/3.2.2/airflow-core/src/airflow/api_fastapi/auth/managers/base_auth_manager.py#L152
The immediately following request succeeds, because the failed query poisons and effectively resets the pooled connection. In production this shows up as intermittent 500s on the first request after overnight idle periods.
Traceback (trimmed)
How to reproduce
SET GLOBAL wait_timeout = 60;wait_timeout(e.g. 90 s) without touching the api-server.OperationalError4031 raised fromRevokedToken.is_revoked. Repeat once more → succeeds.What you think should happen instead?
Root cause
settings.Session.FabAuthManager.deserialize_userleaves a transaction open after the request is served, so the connection stays checked out across requests. Once it idles past MySQL'swait_timeout, the server drops it (error 4031). Because the connection is never returned to the pool, there is no checkout event —pool_pre_ping/pool_recyclecannot help.FabAuthManager.deserialize_user.RevokedToken.is_revokedinget_user_from_tokennow runs beforedeserialize_user, so it is the first thing to touch the poisoned scoped session — and it has no recovery logic. The 500 that fix(fab): recover from first idle MySQL disconnect in token auth #62919 fixed is back, just raised one step earlier in the auth path.What you think should happen instead?
The first request after an idle disconnect should recover transparently, as it does in
deserialize_usersince #62919. The revoked-token check should apply the same recovery: onSQLAlchemyError, discard the scoped session (settings.Session.remove()) and retry once on a fresh connection. Something like:Alternatively, the recovery could be hoisted so it covers the whole
get_user_from_tokenpath instead of being duplicated per call site.Operating System
No response
Deployment
None
Apache Airflow Provider(s)
No response
Versions of Apache Airflow Providers
apache-airflow-providers-fab==3.6.4 (already includes the #62919 fix — it is bypassed, not missing)
Official Helm Chart version
1.22.0 (latest released)
Kubernetes Version
1.25
Helm Chart configuration
No response
Docker Image customizations
No response
Anything else?
wait_timeout; looks intermittent in production.deserialize_user(this check runs earlier and bypasses it)Session.remove()hardening in FAB's cleanup middlewareAre you willing to submit PR?
Code of Conduct