diff --git a/CHANGELOG.md b/CHANGELOG.md index c399e74acfa..b7e234aad3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Add shutdown lifecycle method to `Sampler` to allow resource cleanup for stateful samplers. + ([#8574](https://github.com/open-telemetry/opentelemetry-java/pull/8574)) + ## Version 1.63.0 (2026-06-05) ### API diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-jaeger-remote-sampler.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-jaeger-remote-sampler.txt index 4c66caca34c..0ca5622ab3a 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-jaeger-remote-sampler.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-extension-jaeger-remote-sampler.txt @@ -1,2 +1,4 @@ Comparing source compatibility of opentelemetry-sdk-extension-jaeger-remote-sampler-1.64.0-SNAPSHOT.jar against opentelemetry-sdk-extension-jaeger-remote-sampler-1.63.0.jar -No changes. \ No newline at end of file +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.extension.trace.jaeger.sampler.JaegerRemoteSampler (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.common.CompletableResultCode shutdown() diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt index 069a14f1761..29677806989 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt @@ -1,2 +1,4 @@ Comparing source compatibility of opentelemetry-sdk-trace-1.64.0-SNAPSHOT.jar against opentelemetry-sdk-trace-1.63.0.jar -No changes. \ No newline at end of file +*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.trace.samplers.Sampler (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.common.CompletableResultCode shutdown() diff --git a/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSampler.java b/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSampler.java index 2e32f566273..e26dcabbd46 100644 --- a/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSampler.java +++ b/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSampler.java @@ -8,6 +8,7 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.common.export.GrpcResponse; import io.opentelemetry.sdk.common.export.GrpcSender; import io.opentelemetry.sdk.common.export.GrpcStatusCode; @@ -175,11 +176,16 @@ public static JaegerRemoteSamplerBuilder builder() { return new JaegerRemoteSamplerBuilder(); } + @Override + public CompletableResultCode shutdown() { + pollFuture.cancel(false); + pollExecutor.shutdownNow(); + return grpcSender.shutdown(); + } + @Override @SuppressWarnings("Interruption") public void close() { - pollFuture.cancel(true); - pollExecutor.shutdownNow(); - grpcSender.shutdown().join(10, TimeUnit.SECONDS); + shutdown().join(10, TimeUnit.SECONDS); } } diff --git a/sdk-extensions/jaeger-remote-sampler/src/test/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerTest.java b/sdk-extensions/jaeger-remote-sampler/src/test/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerTest.java index e646a0d4921..969bf04a934 100644 --- a/sdk-extensions/jaeger-remote-sampler/src/test/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerTest.java +++ b/sdk-extensions/jaeger-remote-sampler/src/test/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerTest.java @@ -160,6 +160,20 @@ void connectionWorks() { } } + @Test + void shutdown() { + JaegerRemoteSampler sampler = + JaegerRemoteSampler.builder() + .setEndpoint(server.httpUri().toString()) + .setPollingInterval(1, TimeUnit.SECONDS) + .setServiceName(SERVICE_NAME) + .build(); + + assertThat(sampler).extracting("grpcSender").isInstanceOf(OkHttpGrpcSender.class); + + assertThat(sampler.shutdown().join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + } + @Test void tlsConnectionWorks() throws IOException { try (JaegerRemoteSampler sampler = diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java index 254d78fd254..78c7b48d43a 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java @@ -10,6 +10,7 @@ import io.opentelemetry.sdk.common.internal.ExceptionAttributeResolver; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.samplers.Sampler; +import java.util.Arrays; import java.util.List; import java.util.function.Supplier; import javax.annotation.Nullable; @@ -106,18 +107,28 @@ SdkTracerInstrumentation getTracerInstrumentation() { } /** - * Stops tracing, including shutting down processors and set to {@code true} {@link + * Stops tracing, including shutting down processors and sampler and set to {@code true} {@link * #hasBeenShutdown()}. * - * @return a {@link CompletableResultCode} that will be completed when the span processor is shut - * down. + * @return a {@link CompletableResultCode} that will be completed when the span processor and + * sampler are shut down. */ CompletableResultCode shutdown() { synchronized (lock) { if (shutdownResult != null) { return shutdownResult; } - shutdownResult = activeSpanProcessor.shutdown(); + + CompletableResultCode samplerShutdownResult = sampler.shutdown(); + + shutdownResult = + CompletableResultCode.ofAll( + Arrays.asList( + activeSpanProcessor.shutdown(), + samplerShutdownResult == null + ? CompletableResultCode.ofSuccess() + : samplerShutdownResult)); + return shutdownResult; } } diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/samplers/Sampler.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/samplers/Sampler.java index 73edba3b6f1..0b987d0ac53 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/samplers/Sampler.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/samplers/Sampler.java @@ -11,6 +11,7 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.TraceId; import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.trace.data.LinkData; import java.util.List; import javax.annotation.concurrent.ThreadSafe; @@ -119,4 +120,15 @@ SamplingResult shouldSample( * @return the description of this {@code Sampler}. */ String getDescription(); + + /** + * Shuts down the sampler, releasing any resources held by it. + * + *
This method is called when the {@code SdkTracerProvider} is shut down. + * + * @return a {@link CompletableResultCode} which is completed when shutdown is finished. + */ + default CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } } diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderTest.java index b007fe06b22..4ba416c4f88 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkTracerProviderTest.java @@ -237,6 +237,19 @@ void shutdown() { Mockito.verify(spanProcessor, Mockito.times(1)).shutdown(); } + @Test + void shutdown_ShutsDownSampler() { + Sampler sampler = mock(Sampler.class); + + when(sampler.shutdown()).thenReturn(CompletableResultCode.ofSuccess()); + + SdkTracerProvider tracerProvider = SdkTracerProvider.builder().setSampler(sampler).build(); + + tracerProvider.shutdown(); + + verify(sampler).shutdown(); + } + @Test void close() { tracerProvider.close();