diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt index cc49df57512..3b29b622a14 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt @@ -1,2 +1,7 @@ Comparing source compatibility of opentelemetry-sdk-common-1.41.0-SNAPSHOT.jar against opentelemetry-sdk-common-1.40.0.jar -No changes. \ No newline at end of file +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.common.CompletableResultCode (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.common.CompletableResultCode failExceptionally(java.lang.Throwable) + +++ NEW METHOD: PUBLIC(+) java.lang.Throwable getFailureThrowable() + +++ NEW ANNOTATION: javax.annotation.Nullable + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.common.CompletableResultCode ofExceptionalFailure(java.lang.Throwable) diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/common/CompletableResultCode.java b/sdk/common/src/main/java/io/opentelemetry/sdk/common/CompletableResultCode.java index b7f7fa944fd..a3646343bd5 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/common/CompletableResultCode.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/common/CompletableResultCode.java @@ -13,6 +13,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; /** @@ -33,9 +34,19 @@ public static CompletableResultCode ofFailure() { return FAILURE; } + /** + * Returns a {@link CompletableResultCode} that has been {@link #failExceptionally(Throwable) + * failed exceptionally}. + */ + public static CompletableResultCode ofExceptionalFailure(Throwable throwable) { + return new CompletableResultCode().failExceptionally(throwable); + } + /** * Returns a {@link CompletableResultCode} that completes after all the provided {@link - * CompletableResultCode}s complete. If any of the results fail, the result will be failed. + * CompletableResultCode}s complete. If any of the results fail, the result will be failed. If any + * {@link #failExceptionally(Throwable) failed exceptionally}, the result will be failed + * exceptionally with the first {@link Throwable} from {@code codes}. */ public static CompletableResultCode ofAll(Collection codes) { if (codes.isEmpty()) { @@ -44,15 +55,20 @@ public static CompletableResultCode ofAll(Collection code CompletableResultCode result = new CompletableResultCode(); AtomicInteger pending = new AtomicInteger(codes.size()); AtomicBoolean failed = new AtomicBoolean(); + AtomicReference throwableRef = new AtomicReference<>(); for (CompletableResultCode code : codes) { code.whenComplete( () -> { if (!code.isSuccess()) { failed.set(true); + Throwable codeThrowable = code.getFailureThrowable(); + if (codeThrowable != null) { + throwableRef.compareAndSet(null, codeThrowable); + } } if (pending.decrementAndGet() == 0) { if (failed.get()) { - result.fail(); + result.failInternal(throwableRef.get()); } else { result.succeed(); } @@ -71,6 +87,10 @@ public CompletableResultCode() {} @GuardedBy("lock") private Boolean succeeded = null; + @Nullable + @GuardedBy("lock") + private Throwable throwable = null; + @GuardedBy("lock") private final List completionActions = new ArrayList<>(); @@ -89,11 +109,27 @@ public CompletableResultCode succeed() { return this; } - /** Complete this {@link CompletableResultCode} unsuccessfully if it is not already completed. */ + /** + * Complete this {@link CompletableResultCode} unsuccessfully if it is not already completed, + * setting the {@link #getFailureThrowable() failure throwable} to {@code null}. + */ public CompletableResultCode fail() { + return failInternal(null); + } + + /** + * Completes this {@link CompletableResultCode} unsuccessfully if it is not already completed, + * setting the {@link #getFailureThrowable() failure throwable} to {@code throwable}. + */ + public CompletableResultCode failExceptionally(Throwable throwable) { + return failInternal(throwable); + } + + private CompletableResultCode failInternal(@Nullable Throwable throwable) { synchronized (lock) { if (succeeded == null) { succeeded = false; + this.throwable = throwable; for (Runnable action : completionActions) { action.run(); } @@ -104,7 +140,7 @@ public CompletableResultCode fail() { /** * Obtain the current state of completion. Generally call once completion is achieved via the - * thenRun method. + * {@link #whenComplete(Runnable)} method. * * @return the current state of completion */ @@ -114,6 +150,21 @@ public boolean isSuccess() { } } + /** + * Returns {@link Throwable} if this {@link CompletableResultCode} was {@link + * #failExceptionally(Throwable) failed exceptionally}. Generally call once completion is achieved + * via the {@link #whenComplete(Runnable)} method. + * + * @return the throwable if failed exceptionally, or null if: {@link #fail() failed without + * exception}, {@link #succeed() succeeded}, or not complete. + */ + @Nullable + public Throwable getFailureThrowable() { + synchronized (lock) { + return throwable; + } + } + /** * Perform an action on completion. Actions are guaranteed to be called only once. * diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/common/CompletableResultCodeTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/common/CompletableResultCodeTest.java index 544777430f5..3964fdd7f73 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/common/CompletableResultCodeTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/common/CompletableResultCodeTest.java @@ -21,12 +21,32 @@ class CompletableResultCodeTest { @Test void ofSuccess() { - assertThat(CompletableResultCode.ofSuccess().isSuccess()).isTrue(); + assertThat(CompletableResultCode.ofSuccess()) + .satisfies( + code -> { + assertThat(code.isSuccess()).isTrue(); + assertThat(code.getFailureThrowable()).isNull(); + }); } @Test void ofFailure() { - assertThat(CompletableResultCode.ofFailure().isSuccess()).isFalse(); + assertThat(CompletableResultCode.ofFailure()) + .satisfies( + code -> { + assertThat(code.isSuccess()).isFalse(); + assertThat(code.getFailureThrowable()).isNull(); + }); + } + + @Test + void ofExceptionalFailure() { + assertThat(CompletableResultCode.ofExceptionalFailure(new Exception("error"))) + .satisfies( + code -> { + assertThat(code.isSuccess()).isFalse(); + assertThat(code.getFailureThrowable()).hasMessage("error"); + }); } @Test @@ -149,6 +169,24 @@ void ofAllWithFailure() { .isFalse(); } + @Test + void ofAllWithExceptionalFailure() { + assertThat( + CompletableResultCode.ofAll( + Arrays.asList( + CompletableResultCode.ofSuccess(), + CompletableResultCode.ofFailure(), + CompletableResultCode.ofExceptionalFailure(new Exception("error1")), + CompletableResultCode.ofExceptionalFailure(new Exception("error2")), + CompletableResultCode.ofSuccess()))) + .satisfies( + code -> { + assertThat(code.isSuccess()).isFalse(); + // failure throwable is set to first throwable seen in the collection + assertThat(code.getFailureThrowable()).hasMessage("error1"); + }); + } + @Test void join() { CompletableResultCode result = new CompletableResultCode();