Repository navigation
Race condition in MetricsTracer causes AttributeError: 'NoneType' object has no attribute 'status' #15881
Description
Activity
- addedapi: spannerIssues related to the Spanner API.Issues related to the Spanner API.
on Dec 21, 2025 TL;DR The problem is intermittent, but very frequent when you make ample use of threading. So much so as to make it unusable without turning off this value.
Another workaround is to set the environment variable:
SPANNER_DISABLE_BUILTIN_METRICS=trueMore details on how to reproduce
I am seeing this error quite frequently when deployed to GCP with
google-cloud-spanner==3.62.0under load using uvicorn as the server with FastAPI and this pattern:From the FastAPI endpoint, my code like this:
query_result = asyncio.to_thread(query_table, key=value)The stack trace shows the exception occurring when my code calls:
my_orm = s.query(MyORM).first()from inside the spawned thread with the stack trace ending up the same as the original report.
Unfortunately, when I say "under load" I mean we are running less than 10qps in this test (not a production service) and the frequency of the message is high, a large percentage of my calls fail, meaning (Python + Spanner + threading) is unusable without turning off client side metrics.
Commit that exposed the problem
commit 64aebe7e3ecfec756435f7d102b36f5a41f7cc52 Author: Subham Sinha <35077434+sinhasubham@users.noreply.github.com> Date: Thu Dec 4 11:27:30 2025 +0530Alternate workarounds
Looks like you could downgrade the library to
google_cloud_spanner==3.59.0(I didn't try it)Looking through
google/cloud/spanner_v1/client.pyI saw that we can also set this environment variable to turn it off:SPANNER_DISABLE_BUILTIN_METRICS=true@sinhasubham it looks like you are hard at work on this issue. Thanks for that.
This issue was moved from the following repo as part of the monorepo migration: googleapis/python-spanner
For anyone arriving here with this error instead of the
AttributeErrorrace condition:INVALID_ARGUMENT: 400 One or more TimeSeries could not be written: timeSeries[...]: the set of resource labels is incomplete, missing (instance_id)That is a separate bug — a redundant
MetricsCapture()insidetrace_callthat produces orphan metric data points with incomplete resource labels on every Spanner operation. The thread-safety fix in v3.63.0 (contextvars) resolved theAttributeErrorcrash from this issue, but not theINVALID_ARGUMENTerror.Tracked separately: #16173
Fix: googleapis/python-spanner#1522Hi! Triaging older issues this week — I think this one has shipped and can be closed.
The race condition described here was caused by
SpannerMetricsTracerFactory.current_metrics_tracerbeing a shared class variable. On currentmain, that storage has been replaced with acontextvars.ContextVar, which gives per-request isolation and removes the cross-thread interference described in this report:packages/google-cloud-spanner/google/cloud/spanner_v1/metrics/spanner_metrics_tracer_factory.py:50—_current_metrics_tracer_ctx = contextvars.ContextVar("current_metrics_tracer", default=None)packages/google-cloud-spanner/google/cloud/spanner_v1/metrics/spanner_metrics_tracer_factory.py:95— getter now reads from the ContextVar (_current_metrics_tracer_ctx.get())packages/google-cloud-spanner/google/cloud/spanner_v1/metrics/spanner_metrics_tracer_factory.py:99— setter writes into the ContextVar (_current_metrics_tracer_ctx.set(tracer))
The
enabeldtypo atspanner_metrics_tracer_factory.py:93mentioned in the original report is also no longer present.Would you mind closing this out? If you can still reproduce the
AttributeError: 'NoneType' object has no attribute 'status'against a recentgoogle-cloud-spannerrelease, please share the version and a minimal repro and I'll dig further.(Disclosure: I drafted this comment with help from Claude Code while triaging stale issues; the references above were verified manually against the current
mainbranch.)

Environment details
google-cloud-spannerversion: 3.61.0Steps to reproduce
sqlalchemy-spanner1.17.2) in a multi-threaded environment (e.g., FastAPI with uvicorn workers)Code example
The bug is a race condition in
google/cloud/spanner_v1/metrics/metrics_interceptor.py:SpannerMetricsTracerFactory.current_metrics_traceris a class variable shared across all threads. Betweenrecord_attempt_start()andrecord_attempt_completion(), another thread can callMetricsCapture.__enter__()which replacescurrent_metrics_tracerwith a new instance:When
record_attempt_completion()is called, it operates on a differentMetricsTracerinstance that hasn't hadrecord_attempt_start()called, socurrent_op.current_attemptisNone.Suggested fix: Make
current_metrics_tracerthread-local instead of a class variable:Additional bug: There's a typo in
spanner_metrics_tracer_factory.pyline 93:Stack trace
Workaround: Disable built-in metrics by calling
SpannerMetricsTracerFactory(enabled=False)before any Spanner operations.