diff --git a/api/all/src/main/java/io/opentelemetry/api/GlobalOpenTelemetry.java b/api/all/src/main/java/io/opentelemetry/api/GlobalOpenTelemetry.java index 230a85e06a9..83e70bf7d8b 100644 --- a/api/all/src/main/java/io/opentelemetry/api/GlobalOpenTelemetry.java +++ b/api/all/src/main/java/io/opentelemetry/api/GlobalOpenTelemetry.java @@ -17,6 +17,7 @@ import io.opentelemetry.context.propagation.ContextPropagators; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -116,6 +117,19 @@ public static void set(OpenTelemetry openTelemetry) { } } + /** + * Sets the {@link OpenTelemetry} that should be the global instance. + * + *

This method calls the given {@code supplier} and calls {@link #set(OpenTelemetry)}, all + * while holding the {@link GlobalOpenTelemetry} mutex. + */ + public static void set(Supplier supplier) { + synchronized (mutex) { + OpenTelemetry openTelemetry = supplier.get(); + set(openTelemetry); + } + } + /** Returns the globally registered {@link TracerProvider}. */ public static TracerProvider getTracerProvider() { return get().getTracerProvider(); diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt index ba433bd6ed1..3a33f42b55a 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt @@ -1,2 +1,4 @@ Comparing source compatibility of opentelemetry-api-1.52.0-SNAPSHOT.jar against opentelemetry-api-1.51.0.jar -No changes. \ No newline at end of file +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.api.GlobalOpenTelemetry (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) STATIC(+) void set(java.util.function.Supplier) diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java index e12848f0d81..124231fa325 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java @@ -41,6 +41,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; @@ -424,6 +426,25 @@ AutoConfiguredOpenTelemetrySdkBuilder setComponentLoader(ComponentLoader compone * the settings of this {@link AutoConfiguredOpenTelemetrySdkBuilder}. */ public AutoConfiguredOpenTelemetrySdk build() { + if (!setResultAsGlobal) { + return buildImpl(); + } + AtomicReference autoConfiguredRef = new AtomicReference<>(); + GlobalOpenTelemetry.set( + () -> { + AutoConfiguredOpenTelemetrySdk sdk = buildImpl(); + autoConfiguredRef.set(sdk); + return sdk.getOpenTelemetrySdk(); + }); + AutoConfiguredOpenTelemetrySdk sdk = Objects.requireNonNull(autoConfiguredRef.get()); + logger.log( + Level.FINE, + "Global OpenTelemetry set to {0} by autoconfiguration", + sdk.getOpenTelemetrySdk()); + return sdk; + } + + private AutoConfiguredOpenTelemetrySdk buildImpl() { SpiHelper spiHelper = SpiHelper.create(componentLoader); if (!customized) { customized = true; @@ -440,8 +461,10 @@ public AutoConfiguredOpenTelemetrySdk build() { maybeConfigureFromFile(config, componentLoader); if (fromFileConfiguration != null) { maybeRegisterShutdownHook(fromFileConfiguration.getOpenTelemetrySdk()); - maybeSetAsGlobal( - fromFileConfiguration.getOpenTelemetrySdk(), fromFileConfiguration.getConfigProvider()); + Object configProvider = fromFileConfiguration.getConfigProvider(); + if (setResultAsGlobal && INCUBATOR_AVAILABLE && configProvider != null) { + IncubatingUtil.setGlobalConfigProvider(configProvider); + } return fromFileConfiguration; } @@ -467,7 +490,6 @@ public AutoConfiguredOpenTelemetrySdk build() { OpenTelemetrySdk openTelemetrySdk = sdkBuilder.build(); maybeRegisterShutdownHook(openTelemetrySdk); - maybeSetAsGlobal(openTelemetrySdk, null); callAutoConfigureListeners(spiHelper, openTelemetrySdk); return AutoConfiguredOpenTelemetrySdk.create(openTelemetrySdk, resource, config, null); @@ -572,19 +594,6 @@ private void maybeRegisterShutdownHook(OpenTelemetrySdk openTelemetrySdk) { Runtime.getRuntime().addShutdownHook(shutdownHook(openTelemetrySdk)); } - private void maybeSetAsGlobal( - OpenTelemetrySdk openTelemetrySdk, @Nullable Object configProvider) { - if (!setResultAsGlobal) { - return; - } - GlobalOpenTelemetry.set(openTelemetrySdk); - if (INCUBATOR_AVAILABLE && configProvider != null) { - IncubatingUtil.setGlobalConfigProvider(configProvider); - } - logger.log( - Level.FINE, "Global OpenTelemetry set to {0} by autoconfiguration", openTelemetrySdk); - } - // Visible for testing void callAutoConfigureListeners(SpiHelper spiHelper, OpenTelemetrySdk openTelemetrySdk) { for (AutoConfigureListener listener : spiHelper.getListeners()) { diff --git a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java index c7ae65de9a0..45d26308320 100644 --- a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java +++ b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java @@ -58,6 +58,7 @@ import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.samplers.Sampler; import java.io.IOException; +import java.lang.reflect.Field; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collections; @@ -65,7 +66,13 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Supplier; @@ -674,4 +681,66 @@ void configurationError_ClosesResources() { logs.assertContains("Error closing io.opentelemetry.sdk.trace.SdkTracerProvider: Error!"); } + + @Test + void globalOpenTelemetryLock() throws InterruptedException, ExecutionException, TimeoutException { + CountDownLatch autoconfigStarted = new CountDownLatch(1); + CountDownLatch completeAutoconfig = new CountDownLatch(1); + ExecutorService executorService = Executors.newFixedThreadPool(2); + + // Submit a future to autoconfigure the SDK and set the result as global. Add a customization + // hook which blocks until we say so. + CompletableFuture autoConfiguredOpenTelemetryFuture = + CompletableFuture.supplyAsync( + () -> + builder + .addLoggerProviderCustomizer( + (sdkLoggerProviderBuilder, configProperties) -> { + autoconfigStarted.countDown(); + try { + completeAutoconfig.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return sdkLoggerProviderBuilder; + }) + .setResultAsGlobal() + .build() + .getOpenTelemetrySdk(), + executorService); + + // Wait for autoconfiguration to enter our callback, then try to get an instance of + // GlobalOpenTelemetry. GlobalOpenTelemetry.get() should block until we release the + // completeAutoconfig latch and allow autoconfiguration to complete. + autoconfigStarted.await(); + CompletableFuture globalOpenTelemetryFuture = + CompletableFuture.supplyAsync(GlobalOpenTelemetry::get, executorService); + Thread.sleep(10); + assertThat(globalOpenTelemetryFuture.isDone()).isFalse(); + assertThat(autoConfiguredOpenTelemetryFuture.isDone()).isFalse(); + + // Release the latch, allowing autoconfiguration to complete. Confirm that our + // GlobalOpenTelemetry.get() future resolved to the same instance as autoconfiguration. + completeAutoconfig.countDown(); + assertThat(unobfuscate(globalOpenTelemetryFuture.get(10, TimeUnit.SECONDS))) + .isSameAs(autoConfiguredOpenTelemetryFuture.get(10, TimeUnit.SECONDS)); + + // Cleanup + executorService.shutdown(); + autoConfiguredOpenTelemetryFuture.get().shutdown().join(10, TimeUnit.SECONDS); + GlobalOpenTelemetry.resetForTest(); + } + + private static OpenTelemetry unobfuscate(OpenTelemetry openTelemetry) { + try { + Field delegateField = + Class.forName("io.opentelemetry.api.GlobalOpenTelemetry$ObfuscatedOpenTelemetry") + .getDeclaredField("delegate"); + delegateField.setAccessible(true); + Object delegate = delegateField.get(openTelemetry); + return (OpenTelemetry) delegate; + } catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) { + throw new IllegalStateException("Error unobfuscating OpenTelemetry", e); + } + } }