Skip to content

Commit b0a9deb

Browse files
fandreuzjack-berg
andauthored
Fix race condition of GlobalOpenTelemetry initialization with AutoConfiguredOpenTelemetrySdkBuilder (#7365)
Co-authored-by: Jack Berg <[email protected]> Co-authored-by: jack-berg <[email protected]>
1 parent 76913bb commit b0a9deb

File tree

4 files changed

+111
-17
lines changed

4 files changed

+111
-17
lines changed

api/all/src/main/java/io/opentelemetry/api/GlobalOpenTelemetry.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import io.opentelemetry.context.propagation.ContextPropagators;
1818
import java.lang.reflect.InvocationTargetException;
1919
import java.lang.reflect.Method;
20+
import java.util.function.Supplier;
2021
import java.util.logging.Level;
2122
import java.util.logging.Logger;
2223
import javax.annotation.Nullable;
@@ -116,6 +117,19 @@ public static void set(OpenTelemetry openTelemetry) {
116117
}
117118
}
118119

120+
/**
121+
* Sets the {@link OpenTelemetry} that should be the global instance.
122+
*
123+
* <p>This method calls the given {@code supplier} and calls {@link #set(OpenTelemetry)}, all
124+
* while holding the {@link GlobalOpenTelemetry} mutex.
125+
*/
126+
public static void set(Supplier<OpenTelemetry> supplier) {
127+
synchronized (mutex) {
128+
OpenTelemetry openTelemetry = supplier.get();
129+
set(openTelemetry);
130+
}
131+
}
132+
119133
/** Returns the globally registered {@link TracerProvider}. */
120134
public static TracerProvider getTracerProvider() {
121135
return get().getTracerProvider();
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
Comparing source compatibility of opentelemetry-api-1.52.0-SNAPSHOT.jar against opentelemetry-api-1.51.0.jar
2-
No changes.
2+
*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.api.GlobalOpenTelemetry (not serializable)
3+
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
4+
+++ NEW METHOD: PUBLIC(+) STATIC(+) void set(java.util.function.Supplier<io.opentelemetry.api.OpenTelemetry>)

sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
import java.util.HashMap;
4242
import java.util.List;
4343
import java.util.Map;
44+
import java.util.Objects;
45+
import java.util.concurrent.atomic.AtomicReference;
4446
import java.util.function.BiFunction;
4547
import java.util.function.Function;
4648
import java.util.function.Supplier;
@@ -424,6 +426,25 @@ AutoConfiguredOpenTelemetrySdkBuilder setComponentLoader(ComponentLoader compone
424426
* the settings of this {@link AutoConfiguredOpenTelemetrySdkBuilder}.
425427
*/
426428
public AutoConfiguredOpenTelemetrySdk build() {
429+
if (!setResultAsGlobal) {
430+
return buildImpl();
431+
}
432+
AtomicReference<AutoConfiguredOpenTelemetrySdk> autoConfiguredRef = new AtomicReference<>();
433+
GlobalOpenTelemetry.set(
434+
() -> {
435+
AutoConfiguredOpenTelemetrySdk sdk = buildImpl();
436+
autoConfiguredRef.set(sdk);
437+
return sdk.getOpenTelemetrySdk();
438+
});
439+
AutoConfiguredOpenTelemetrySdk sdk = Objects.requireNonNull(autoConfiguredRef.get());
440+
logger.log(
441+
Level.FINE,
442+
"Global OpenTelemetry set to {0} by autoconfiguration",
443+
sdk.getOpenTelemetrySdk());
444+
return sdk;
445+
}
446+
447+
private AutoConfiguredOpenTelemetrySdk buildImpl() {
427448
SpiHelper spiHelper = SpiHelper.create(componentLoader);
428449
if (!customized) {
429450
customized = true;
@@ -440,8 +461,10 @@ public AutoConfiguredOpenTelemetrySdk build() {
440461
maybeConfigureFromFile(config, componentLoader);
441462
if (fromFileConfiguration != null) {
442463
maybeRegisterShutdownHook(fromFileConfiguration.getOpenTelemetrySdk());
443-
maybeSetAsGlobal(
444-
fromFileConfiguration.getOpenTelemetrySdk(), fromFileConfiguration.getConfigProvider());
464+
Object configProvider = fromFileConfiguration.getConfigProvider();
465+
if (setResultAsGlobal && INCUBATOR_AVAILABLE && configProvider != null) {
466+
IncubatingUtil.setGlobalConfigProvider(configProvider);
467+
}
445468
return fromFileConfiguration;
446469
}
447470

@@ -467,7 +490,6 @@ public AutoConfiguredOpenTelemetrySdk build() {
467490

468491
OpenTelemetrySdk openTelemetrySdk = sdkBuilder.build();
469492
maybeRegisterShutdownHook(openTelemetrySdk);
470-
maybeSetAsGlobal(openTelemetrySdk, null);
471493
callAutoConfigureListeners(spiHelper, openTelemetrySdk);
472494

473495
return AutoConfiguredOpenTelemetrySdk.create(openTelemetrySdk, resource, config, null);
@@ -572,19 +594,6 @@ private void maybeRegisterShutdownHook(OpenTelemetrySdk openTelemetrySdk) {
572594
Runtime.getRuntime().addShutdownHook(shutdownHook(openTelemetrySdk));
573595
}
574596

575-
private void maybeSetAsGlobal(
576-
OpenTelemetrySdk openTelemetrySdk, @Nullable Object configProvider) {
577-
if (!setResultAsGlobal) {
578-
return;
579-
}
580-
GlobalOpenTelemetry.set(openTelemetrySdk);
581-
if (INCUBATOR_AVAILABLE && configProvider != null) {
582-
IncubatingUtil.setGlobalConfigProvider(configProvider);
583-
}
584-
logger.log(
585-
Level.FINE, "Global OpenTelemetry set to {0} by autoconfiguration", openTelemetrySdk);
586-
}
587-
588597
// Visible for testing
589598
void callAutoConfigureListeners(SpiHelper spiHelper, OpenTelemetrySdk openTelemetrySdk) {
590599
for (AutoConfigureListener listener : spiHelper.getListeners()) {

sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,21 @@
5858
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
5959
import io.opentelemetry.sdk.trace.samplers.Sampler;
6060
import java.io.IOException;
61+
import java.lang.reflect.Field;
6162
import java.math.BigDecimal;
6263
import java.math.BigInteger;
6364
import java.util.Collections;
6465
import java.util.HashMap;
6566
import java.util.List;
6667
import java.util.Map;
6768
import java.util.Properties;
69+
import java.util.concurrent.CompletableFuture;
70+
import java.util.concurrent.CountDownLatch;
71+
import java.util.concurrent.ExecutionException;
72+
import java.util.concurrent.ExecutorService;
73+
import java.util.concurrent.Executors;
6874
import java.util.concurrent.TimeUnit;
75+
import java.util.concurrent.TimeoutException;
6976
import java.util.function.BiFunction;
7077
import java.util.function.Consumer;
7178
import java.util.function.Supplier;
@@ -674,4 +681,66 @@ void configurationError_ClosesResources() {
674681

675682
logs.assertContains("Error closing io.opentelemetry.sdk.trace.SdkTracerProvider: Error!");
676683
}
684+
685+
@Test
686+
void globalOpenTelemetryLock() throws InterruptedException, ExecutionException, TimeoutException {
687+
CountDownLatch autoconfigStarted = new CountDownLatch(1);
688+
CountDownLatch completeAutoconfig = new CountDownLatch(1);
689+
ExecutorService executorService = Executors.newFixedThreadPool(2);
690+
691+
// Submit a future to autoconfigure the SDK and set the result as global. Add a customization
692+
// hook which blocks until we say so.
693+
CompletableFuture<OpenTelemetrySdk> autoConfiguredOpenTelemetryFuture =
694+
CompletableFuture.supplyAsync(
695+
() ->
696+
builder
697+
.addLoggerProviderCustomizer(
698+
(sdkLoggerProviderBuilder, configProperties) -> {
699+
autoconfigStarted.countDown();
700+
try {
701+
completeAutoconfig.await();
702+
} catch (InterruptedException e) {
703+
Thread.currentThread().interrupt();
704+
}
705+
return sdkLoggerProviderBuilder;
706+
})
707+
.setResultAsGlobal()
708+
.build()
709+
.getOpenTelemetrySdk(),
710+
executorService);
711+
712+
// Wait for autoconfiguration to enter our callback, then try to get an instance of
713+
// GlobalOpenTelemetry. GlobalOpenTelemetry.get() should block until we release the
714+
// completeAutoconfig latch and allow autoconfiguration to complete.
715+
autoconfigStarted.await();
716+
CompletableFuture<OpenTelemetry> globalOpenTelemetryFuture =
717+
CompletableFuture.supplyAsync(GlobalOpenTelemetry::get, executorService);
718+
Thread.sleep(10);
719+
assertThat(globalOpenTelemetryFuture.isDone()).isFalse();
720+
assertThat(autoConfiguredOpenTelemetryFuture.isDone()).isFalse();
721+
722+
// Release the latch, allowing autoconfiguration to complete. Confirm that our
723+
// GlobalOpenTelemetry.get() future resolved to the same instance as autoconfiguration.
724+
completeAutoconfig.countDown();
725+
assertThat(unobfuscate(globalOpenTelemetryFuture.get(10, TimeUnit.SECONDS)))
726+
.isSameAs(autoConfiguredOpenTelemetryFuture.get(10, TimeUnit.SECONDS));
727+
728+
// Cleanup
729+
executorService.shutdown();
730+
autoConfiguredOpenTelemetryFuture.get().shutdown().join(10, TimeUnit.SECONDS);
731+
GlobalOpenTelemetry.resetForTest();
732+
}
733+
734+
private static OpenTelemetry unobfuscate(OpenTelemetry openTelemetry) {
735+
try {
736+
Field delegateField =
737+
Class.forName("io.opentelemetry.api.GlobalOpenTelemetry$ObfuscatedOpenTelemetry")
738+
.getDeclaredField("delegate");
739+
delegateField.setAccessible(true);
740+
Object delegate = delegateField.get(openTelemetry);
741+
return (OpenTelemetry) delegate;
742+
} catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) {
743+
throw new IllegalStateException("Error unobfuscating OpenTelemetry", e);
744+
}
745+
}
677746
}

0 commit comments

Comments
 (0)