Skip to content

Commit d21c838

Browse files
committed
fix(tracing): ensure CompositeTracer scopes are closed safely in LIFO order
1 parent 42f4a88 commit d21c838

File tree

3 files changed

+88
-6
lines changed

3 files changed

+88
-6
lines changed

sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeTracer.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,36 @@ public CompositeTracer(List<ApiTracer> children) {
5353
public Scope inScope() {
5454
final List<Scope> childScopes = new ArrayList<>(children.size());
5555

56-
for (ApiTracer child : children) {
57-
childScopes.add(child.inScope());
56+
try {
57+
for (ApiTracer child : children) {
58+
childScopes.add(child.inScope());
59+
}
60+
} catch (RuntimeException e) {
61+
for (int i = childScopes.size() - 1; i >= 0; i--) {
62+
try {
63+
childScopes.get(i).close();
64+
} catch (RuntimeException suppressed) {
65+
e.addSuppressed(suppressed);
66+
}
67+
}
68+
throw e;
5869
}
5970

6071
return () -> {
61-
for (Scope childScope : childScopes) {
62-
childScope.close();
72+
RuntimeException exception = null;
73+
for (int i = childScopes.size() - 1; i >= 0; i--) {
74+
try {
75+
childScopes.get(i).close();
76+
} catch (RuntimeException e) {
77+
if (exception == null) {
78+
exception = e;
79+
} else {
80+
exception.addSuppressed(e);
81+
}
82+
}
83+
}
84+
if (exception != null) {
85+
throw exception;
6386
}
6487
};
6588
}

sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/SpanTracer.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@ public SpanTracer(Tracer tracer, ApiTracerContext apiTracerContext) {
8585
buildAttributes();
8686
}
8787

88+
@Override
89+
public Scope inScope() {
90+
if (attemptSpan == null) {
91+
return () -> {};
92+
}
93+
final io.opentelemetry.context.Scope otelScope = attemptSpan.makeCurrent();
94+
return () -> otelScope.close();
95+
}
96+
8897
private static String resolveAttemptSpanName(ApiTracerContext apiTracerContext) {
8998
if (apiTracerContext.transport() == ApiTracerContext.Transport.GRPC) {
9099
// gRPC Uses the full method name as span name.

sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/tracing/CompositeTracerTest.java

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
*/
3030
package com.google.api.gax.tracing;
3131

32+
import static org.junit.jupiter.api.Assertions.assertEquals;
33+
import static org.junit.jupiter.api.Assertions.assertThrows;
34+
import static org.junit.jupiter.api.Assertions.assertTrue;
35+
import static org.mockito.Mockito.doThrow;
36+
import static org.mockito.Mockito.inOrder;
3237
import static org.mockito.Mockito.mock;
3338
import static org.mockito.Mockito.verify;
3439
import static org.mockito.Mockito.when;
@@ -38,6 +43,7 @@
3843
import java.util.Map;
3944
import org.junit.jupiter.api.BeforeEach;
4045
import org.junit.jupiter.api.Test;
46+
import org.mockito.InOrder;
4147

4248
class CompositeTracerTest {
4349

@@ -53,7 +59,7 @@ void setUp() {
5359
}
5460

5561
@Test
56-
void testInScope() {
62+
void testInScope_lifoOrder() {
5763
ApiTracer.Scope scope1 = mock(ApiTracer.Scope.class);
5864
ApiTracer.Scope scope2 = mock(ApiTracer.Scope.class);
5965

@@ -63,10 +69,54 @@ void testInScope() {
6369
ApiTracer.Scope compositeScope = compositeTracer.inScope();
6470
compositeScope.close();
6571

72+
verify(child1).inScope();
73+
verify(child2).inScope();
74+
75+
InOrder inOrder = inOrder(scope2, scope1);
76+
inOrder.verify(scope2).close();
77+
inOrder.verify(scope1).close();
78+
}
79+
80+
@Test
81+
void testInScope_childInScopeThrows() {
82+
ApiTracer.Scope scope1 = mock(ApiTracer.Scope.class);
83+
RuntimeException exception = new RuntimeException("Runtime Error");
84+
85+
when(child1.inScope()).thenReturn(scope1);
86+
when(child2.inScope()).thenThrow(exception);
87+
88+
RuntimeException thrown = assertThrows(RuntimeException.class, () -> compositeTracer.inScope());
89+
90+
assertEquals(exception, thrown);
6691
verify(child1).inScope();
6792
verify(child2).inScope();
6893
verify(scope1).close();
69-
verify(scope2).close();
94+
}
95+
96+
@Test
97+
void testInScope_childScopeCloseThrows() {
98+
ApiTracer.Scope scope1 = mock(ApiTracer.Scope.class);
99+
ApiTracer.Scope scope2 = mock(ApiTracer.Scope.class);
100+
101+
RuntimeException exception2 = new RuntimeException("Scope 2 close Error");
102+
RuntimeException exception1 = new RuntimeException("Scope 1 close Error");
103+
104+
when(child1.inScope()).thenReturn(scope1);
105+
when(child2.inScope()).thenReturn(scope2);
106+
107+
doThrow(exception2).when(scope2).close();
108+
doThrow(exception1).when(scope1).close();
109+
110+
ApiTracer.Scope compositeScope = compositeTracer.inScope();
111+
112+
RuntimeException thrown = assertThrows(RuntimeException.class, () -> compositeScope.close());
113+
114+
assertEquals(exception2, thrown);
115+
assertTrue(Arrays.asList(thrown.getSuppressed()).contains(exception1));
116+
117+
InOrder inOrder = inOrder(scope2, scope1);
118+
inOrder.verify(scope2).close();
119+
inOrder.verify(scope1).close();
70120
}
71121

72122
@Test

0 commit comments

Comments
 (0)