Skip to content

fix(spanner): avoid data race on DIRECTPATH_CHANNEL_CREATED by using volatile#13727

Open
rahul2393 wants to merge 1 commit into
googleapis:mainfrom
rahul2393:fm/fix-directpath-tsan-x9
Open

fix(spanner): avoid data race on DIRECTPATH_CHANNEL_CREATED by using volatile#13727
rahul2393 wants to merge 1 commit into
googleapis:mainfrom
rahul2393:fm/fix-directpath-tsan-x9

Conversation

@rahul2393

@rahul2393 rahul2393 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Fixes b/509632639.

Problem

GapicSpannerRpc declared a process-wide mutable static flag:

public static boolean DIRECTPATH_CHANNEL_CREATED = false;

The flag is written from the GapicSpannerRpc constructor without synchronization and read by HeaderInterceptor when populating the directpath_enabled built-in metrics attribute. When multiple Spanner clients are constructed concurrently (for example Apache Beam's DirectRunner initializing DoFns on multiple worker threads), the unsynchronized write/read pair is a data race; ThreadSanitizer halts the JVM on it.

Fix

Declare the field volatile:

public static volatile boolean DIRECTPATH_CHANNEL_CREATED = false;
  • The volatile write in the constructor happens-before every subsequent volatile read in HeaderInterceptor, eliminating the data race. The flag has no read-modify-write usage, so volatile is sufficient; the existing last-writer-wins semantics are preserved unchanged.
  • volatile was chosen over AtomicBoolean deliberately: it keeps the field's source and binary signature (boolean) intact, so any external code reading GapicSpannerRpc.DIRECTPATH_CHANNEL_CREATED keeps compiling and linking. (The class is @InternalApi and the spi.v1 package is excluded from clirr checks, but there is no reason to break the field's ABI when volatile fixes the race equally well.)
  • Javadoc on the field documents the concurrency semantics.

…volatile

GapicSpannerRpc declared a process-wide mutable static flag
'public static boolean DIRECTPATH_CHANNEL_CREATED' that is written from
the GapicSpannerRpc constructor without synchronization and read by
HeaderInterceptor when populating the directpath_enabled built-in
metrics attribute. Constructing multiple Spanner clients concurrently
(for example Apache Beam DirectRunner initializing DoFns on multiple
worker threads) makes that write/read pair a data race, which
ThreadSanitizer reports and halts the JVM on.

Declare the field volatile so the constructor write happens-before
every subsequent read. The flag has no read-modify-write usage, so
volatile is sufficient, and it preserves the field's boolean source and
binary signature as well as the existing last-writer-wins behavior.
Document the concurrency semantics on the field.

Add a regression test that constructs eight GapicSpannerRpc instances
concurrently against the in-process mock server, verifies construction
succeeds with the flag in a consistent state, and asserts via
reflection that the field stays volatile.
@rahul2393 rahul2393 requested review from a team as code owners July 10, 2026 18:53

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses a potential data race during concurrent client construction by marking the static field DIRECTPATH_CHANNEL_CREATED as volatile in GapicSpannerRpc, and introduces a concurrent unit test to verify this fix. The review feedback recommends improving the test's cleanup block by adding a null check and awaiting the termination of the ExecutorService to prevent potential thread leaks and NullPointerExceptions.

@rahul2393 rahul2393 requested a review from sakthivelmanii July 10, 2026 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant