From 434d28a8308f880412335a1911d34b608b696984 Mon Sep 17 00:00:00 2001 From: Jin Seop Kim Date: Fri, 31 Jul 2026 16:16:09 -0400 Subject: [PATCH 1/3] feat(gax): implement active scope management and attribute validation for T3 attempt spans --- .../tracing/OpenTelemetryTracingTracer.java | 30 ++++++++++++++----- .../OpenTelemetryTracingTracerTest.java | 28 +++++++++++++++++ 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingTracer.java index cb41da9ccbeb..5464ccfaaa02 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingTracer.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingTracer.java @@ -35,6 +35,7 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import java.util.HashMap; import java.util.Map; @@ -53,6 +54,7 @@ class OpenTelemetryTracingTracer implements ApiTracer { private final String attemptSpanName; private final ApiTracerContext apiTracerContext; private @Nullable Span attemptSpan; + private io.opentelemetry.context.@Nullable Scope attemptScope; @Override public void injectTraceContext(java.util.Map carrier) { @@ -146,6 +148,13 @@ public void attemptStarted(Object request, int attemptNumber) { spanBuilder.setAllAttributes(ObservabilityUtils.toOtelAttributes(currentAttemptAttributes)); this.attemptSpan = spanBuilder.startSpan(); + if (this.attemptSpan != null) { + io.opentelemetry.context.Context otelContext = + io.opentelemetry.context.Context.current().with(this.attemptSpan); + if (otelContext != null) { + this.attemptScope = otelContext.makeCurrent(); + } + } } @Override @@ -225,21 +234,26 @@ private void recordErrorAndEndAttempt(@Nullable Throwable error) { attemptSpan.setAllAttributes(ObservabilityUtils.toOtelAttributes(responseAttributes)); } - if (error != null && !Strings.isNullOrEmpty(error.getMessage())) { - attemptSpan.setAttribute( - ObservabilityAttributes.STATUS_MESSAGE_ATTRIBUTE, error.getMessage()); + if (error != null) { + attemptSpan.setStatus(StatusCode.ERROR); + if (!Strings.isNullOrEmpty(error.getMessage())) { + attemptSpan.setAttribute( + ObservabilityAttributes.STATUS_MESSAGE_ATTRIBUTE, error.getMessage()); + } } endAttempt(); } private void endAttempt() { - if (attemptSpan == null) { - return; + if (attemptScope != null) { + attemptScope.close(); + attemptScope = null; + } + if (attemptSpan != null) { + attemptSpan.end(); + attemptSpan = null; } - - attemptSpan.end(); - attemptSpan = null; } @Override diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingTracerTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingTracerTest.java index 33fa2efcc0da..6ba3639298ce 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingTracerTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingTracerTest.java @@ -33,6 +33,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -73,6 +74,12 @@ void setUp() { when(spanBuilder.setSpanKind(any(SpanKind.class))).thenReturn(spanBuilder); when(spanBuilder.setAllAttributes(any(Attributes.class))).thenReturn(spanBuilder); when(spanBuilder.startSpan()).thenReturn(span); + lenient() + .when(span.storeInContext(any())) + .thenAnswer( + invocation -> + ((io.opentelemetry.context.Context) invocation.getArgument(0)) + .with(io.opentelemetry.context.ContextKey.named("test-attempt-span"), span)); openTelemetryTracingTracer = new OpenTelemetryTracingTracer(tracer, ApiTracerContext.empty(), ATTEMPT_SPAN_NAME); } @@ -87,6 +94,27 @@ void testAttemptLifecycle_startsAndEndsAttemptSpan() { verify(span).end(); } + @Test + void testAttemptStarted_setsActiveScope() { + io.opentelemetry.context.Context currentBefore = io.opentelemetry.context.Context.current(); + openTelemetryTracingTracer.attemptStarted(new Object(), 1); + io.opentelemetry.context.Context currentDuring = io.opentelemetry.context.Context.current(); + assertThat(currentDuring).isNotSameInstanceAs(currentBefore); + + openTelemetryTracingTracer.attemptSucceeded(); + assertThat(io.opentelemetry.context.Context.current()).isSameInstanceAs(currentBefore); + } + + @Test + void testAttemptFailed_setsErrorStatus() { + openTelemetryTracingTracer.attemptStarted(new Object(), 1); + openTelemetryTracingTracer.attemptFailedDuration( + new RuntimeException("Test error"), java.time.Duration.ofSeconds(1)); + + verify(span).setStatus(io.opentelemetry.api.trace.StatusCode.ERROR); + verify(span).end(); + } + @Test void testAttemptSucceeded_grpc() { ApiTracerContext context = From 5cf804b1b303a3fc5252ffd9c77c43d706566583 Mon Sep 17 00:00:00 2001 From: Jin Seop Kim Date: Fri, 31 Jul 2026 16:23:09 -0400 Subject: [PATCH 2/3] fix(gax): remove cross-thread Scope handle from OpenTelemetryTracingTracer to resolve thread safety issue --- .../tracing/OpenTelemetryTracingTracer.java | 11 ----------- .../OpenTelemetryTracingTracerTest.java | 18 ------------------ 2 files changed, 29 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingTracer.java index 5464ccfaaa02..9ef300d6323d 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingTracer.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/OpenTelemetryTracingTracer.java @@ -148,13 +148,6 @@ public void attemptStarted(Object request, int attemptNumber) { spanBuilder.setAllAttributes(ObservabilityUtils.toOtelAttributes(currentAttemptAttributes)); this.attemptSpan = spanBuilder.startSpan(); - if (this.attemptSpan != null) { - io.opentelemetry.context.Context otelContext = - io.opentelemetry.context.Context.current().with(this.attemptSpan); - if (otelContext != null) { - this.attemptScope = otelContext.makeCurrent(); - } - } } @Override @@ -246,10 +239,6 @@ private void recordErrorAndEndAttempt(@Nullable Throwable error) { } private void endAttempt() { - if (attemptScope != null) { - attemptScope.close(); - attemptScope = null; - } if (attemptSpan != null) { attemptSpan.end(); attemptSpan = null; diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingTracerTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingTracerTest.java index 6ba3639298ce..d4aa18ce6176 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingTracerTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/OpenTelemetryTracingTracerTest.java @@ -33,7 +33,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -74,12 +73,6 @@ void setUp() { when(spanBuilder.setSpanKind(any(SpanKind.class))).thenReturn(spanBuilder); when(spanBuilder.setAllAttributes(any(Attributes.class))).thenReturn(spanBuilder); when(spanBuilder.startSpan()).thenReturn(span); - lenient() - .when(span.storeInContext(any())) - .thenAnswer( - invocation -> - ((io.opentelemetry.context.Context) invocation.getArgument(0)) - .with(io.opentelemetry.context.ContextKey.named("test-attempt-span"), span)); openTelemetryTracingTracer = new OpenTelemetryTracingTracer(tracer, ApiTracerContext.empty(), ATTEMPT_SPAN_NAME); } @@ -94,17 +87,6 @@ void testAttemptLifecycle_startsAndEndsAttemptSpan() { verify(span).end(); } - @Test - void testAttemptStarted_setsActiveScope() { - io.opentelemetry.context.Context currentBefore = io.opentelemetry.context.Context.current(); - openTelemetryTracingTracer.attemptStarted(new Object(), 1); - io.opentelemetry.context.Context currentDuring = io.opentelemetry.context.Context.current(); - assertThat(currentDuring).isNotSameInstanceAs(currentBefore); - - openTelemetryTracingTracer.attemptSucceeded(); - assertThat(io.opentelemetry.context.Context.current()).isSameInstanceAs(currentBefore); - } - @Test void testAttemptFailed_setsErrorStatus() { openTelemetryTracingTracer.attemptStarted(new Object(), 1); From aa33fecb5ddd6d2e3cb8d780ddcd39634afa89ed Mon Sep 17 00:00:00 2001 From: Jin Seop Kim Date: Fri, 31 Jul 2026 17:25:14 -0400 Subject: [PATCH 3/3] ci: re-trigger GitHub Actions checks