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
I determined this is the correct repository in which to report this bug.
Summary of the issue
A synchronous Firestore call (gRPC transport) made from inside a request-handling
thread switches/unbinds that thread's contextvars Context partway through. Any
framework that stores per-request state in a ContextVar (Flask 2.3+ keeps its request
context in _cv_request) sees that state disappear on the same thread immediately
after the Firestore call. In our Flask app this surfaces as:
RuntimeError: Working outside of request context
raised during response finalization — a 500 to the client, even though the view code
completed successfully.
Firebase support directed us here, and pointed to a similar regression in the gRPC repo: grpc/grpc#40123 ("contextvar loss when using thread pool executors in newer gRPC
versions"). Filing so the Cloud Firestore team can determine whether this is global gRPC
behavior or specific to the Firestore client's transport.
Environment
Python: 3.9
google-cloud-firestore: 2.18.0
firebase-admin: 6.5.0 (thin wrapper over the client above)
google-api-core: 2.19.2
grpcio / grpcio-status: 1.66.1
Transport: default (gRPC)
Web stack: Flask 2.3.0 / Werkzeug 3.0.3, served by gunicorn sync worker
(one request at a time per worker process; stable OS thread)
Client init: module-level firestore.client() (channel created lazily on first RPC)
Observed behavior
We instrumented contextvars.copy_context and logged has_request_context() (hrc)
with a full stack at each call. Within a single request, on the same OS thread:
(A) First Firestore call — hrc is True (context healthy). Fires at channel creation:
Between (A) and (B) our code runs only in-process work (SQLAlchemy queries + a list
comprehension) — nothing that touches contextvars, spawns threads, or uses
asyncio/greenlets. The only machinery that manipulates context between the two points
is the Firestore/gRPC transport (_run_channel_spin_thread / cygrpc.ForkManagedThread
at channel creation, and segregated_call on subsequent RPCs).
Expected vs actual
Expected: a synchronous Firestore RPC leaves the calling thread's contextvars
Context unchanged.
Actual: after the RPC, the calling thread is running in a different Context; any ContextVar set before the call (e.g. Flask's _cv_request) reads as unset.
Reproduction
Flask 2.3 app served by gunicorn sync worker. In a view: log has_request_context(),
do a synchronous Firestore read+write (doc_ref.get() then batch.commit()), log has_request_context() again. On the first Firestore-touching request after a worker
starts (fresh gRPC channel), the second check is False and Flask raises "Working outside
of request context" during response finalization. Minimal standalone repro available on
request.
Questions
Is the Firestore Python client (or the underlying gRPC transport) expected to alter
the calling thread's contextvars Context during a synchronous RPC?
Pre-creating/warming the gRPC channel at process (post-fork) startup — does that
help, or does segregated_call disturb context on every RPC regardless of channel
age?
Using the REST transport (transport="rest") — is that a supported path to avoid
the gRPC thread/context machinery entirely?
A known-good grpcio version to pin below the regression, if this is version-specific?
Current workaround
We run all Firestore calls on a short-lived daemon thread so the context churn lands on
a disposable thread and the request thread's context stays intact. This resolves the
500s, but we would prefer a supported fix rather than thread-isolating every call.
Impact
Intermittent 500s (correlated with the first Firestore call per worker / gRPC channel
creation) on any endpoint that makes a synchronous Firestore call, in production.
Determine this is the right repository
Summary of the issue
A synchronous Firestore call (gRPC transport) made from inside a request-handling
thread switches/unbinds that thread's
contextvarsContext partway through. Anyframework that stores per-request state in a
ContextVar(Flask 2.3+ keeps its requestcontext in
_cv_request) sees that state disappear on the same thread immediatelyafter the Firestore call. In our Flask app this surfaces as:
raised during response finalization — a 500 to the client, even though the view code
completed successfully.
Firebase support directed us here, and pointed to a similar regression in the gRPC repo:
grpc/grpc#40123 ("contextvar loss when using thread pool executors in newer gRPC
versions"). Filing so the Cloud Firestore team can determine whether this is global gRPC
behavior or specific to the Firestore client's transport.
Environment
(one request at a time per worker process; stable OS thread)
firestore.client()(channel created lazily on first RPC)Observed behavior
We instrumented
contextvars.copy_contextand loggedhas_request_context()(hrc)with a full stack at each call. Within a single request, on the same OS thread:
(A) First Firestore call — hrc is True (context healthy). Fires at channel creation:
(B) A later Firestore call in the SAME request — hrc is now False (context gone):
Between (A) and (B) our code runs only in-process work (SQLAlchemy queries + a list
comprehension) — nothing that touches
contextvars, spawns threads, or usesasyncio/greenlets. The only machinery that manipulates context between the two points
is the Firestore/gRPC transport (
_run_channel_spin_thread/cygrpc.ForkManagedThreadat channel creation, and
segregated_callon subsequent RPCs).Expected vs actual
contextvarsContext unchanged.
ContextVarset before the call (e.g. Flask's_cv_request) reads as unset.Reproduction
Flask 2.3 app served by gunicorn sync worker. In a view: log
has_request_context(),do a synchronous Firestore read+write (
doc_ref.get()thenbatch.commit()), loghas_request_context()again. On the first Firestore-touching request after a workerstarts (fresh gRPC channel), the second check is False and Flask raises "Working outside
of request context" during response finalization. Minimal standalone repro available on
request.
Questions
the calling thread's
contextvarsContext during a synchronous RPC?ForkManagedThread/segregated_callusage?help, or does
segregated_calldisturb context on every RPC regardless of channelage?
transport="rest") — is that a supported path to avoidthe gRPC thread/context machinery entirely?
grpcioversion to pin below the regression, if this is version-specific?Current workaround
We run all Firestore calls on a short-lived daemon thread so the context churn lands on
a disposable thread and the request thread's context stays intact. This resolves the
500s, but we would prefer a supported fix rather than thread-isolating every call.
Impact
Intermittent 500s (correlated with the first Firestore call per worker / gRPC channel
creation) on any endpoint that makes a synchronous Firestore call, in production.