Skip to content

Replacing CompletableFuture by CompletableResultCode#2670

Merged
trask merged 3 commits intoopen-telemetry:mainfrom
LikeTheSalad:android-api21-compatibility
Mar 4, 2026
Merged

Replacing CompletableFuture by CompletableResultCode#2670
trask merged 3 commits intoopen-telemetry:mainfrom
LikeTheSalad:android-api21-compatibility

Conversation

@LikeTheSalad
Copy link
Contributor

java.util.concurrent.CompletableFuture is only available from Android API 24+. Android's desugar_jdk_libs does not desugar CompletableFuture, which causes this issue: open-toast/gummy-bears#168

This was confirmed by reproducing the crash on an API 21 emulator.

To avoid this issue, I replaced all CompletableFuture usage with CompletableResultCode from the upstream Java SDK.

@github-actions github-actions bot requested a review from zeitlinger March 4, 2026 13:54
@LikeTheSalad LikeTheSalad marked this pull request as ready for review March 4, 2026 14:09
@LikeTheSalad LikeTheSalad requested a review from a team as a code owner March 4, 2026 14:09
Copilot AI review requested due to automatic review settings March 4, 2026 14:09
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the disk-buffering module to avoid java.util.concurrent.CompletableFuture (not available on older Android API levels and not desugared) by switching async result handling to OpenTelemetry SDK’s CompletableResultCode.

Changes:

  • Replaced CompletableFuture<WriteResult> with CompletableResultCode in SignalStorage and all implementations/callers.
  • Removed WriteResult / DefaultWriteResult types and updated storage/exporter logic accordingly.
  • Updated tests and renamed exporter timeout constants to align with “write” semantics.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporters/SignalStorageExporterTest.java Updates tests to reflect CompletableResultCode-based storage results.
disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/result/WriteResult.java Removes legacy write result abstraction (no longer needed).
disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/result/DefaultWriteResult.java Removes legacy WriteResult implementation.
disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileSpanStorage.java Updates storage API to return CompletableResultCode.
disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileMetricStorage.java Updates storage API to return CompletableResultCode.
disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileLogRecordStorage.java Updates storage API to return CompletableResultCode.
disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/SignalStorage.java Public API change: write/clear now return CompletableResultCode.
disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FileSignalStorage.java Updates write/clear implementations to produce CompletableResultCode outcomes.
disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporters/SignalStorageExporter.java Switches exporter write handling from Future.get(timeout) to CompletableResultCode.join(timeout).
disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SpanToDiskExporter.java Renames default timeout constant to DEFAULT_WRITE_TIMEOUT.
disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/MetricToDiskExporter.java Renames default timeout constant to DEFAULT_WRITE_TIMEOUT.
disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/LogRecordToDiskExporter.java Renames default timeout constant to DEFAULT_WRITE_TIMEOUT.
Comments suppressed due to low confidence (1)

disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporters/SignalStorageExporterTest.java:83

  • This test suite doesn’t cover the timeout path anymore. Since exportToStorage now relies on CompletableResultCode.join(timeout, unit), it’s important to assert behavior when the returned CompletableResultCode does not complete before the timeout (e.g., storage returns a new, never-completed CompletableResultCode). Add a test that verifies onExportError receives a timeout-related throwable and that the returned result code reflects that timeout.
  @Test
  void verifyExportToStorage_failure() {
    SignalStorage.Span storage = mock();
    SignalStorageExporter<SpanData> storageExporter =
        new SignalStorageExporter<>(storage, callback, Duration.ofSeconds(1));
    SpanData item1 = mock();

    // Without exception
    when(storage.write(anyCollection())).thenReturn(CompletableResultCode.ofFailure());

    List<SpanData> items = Collections.singletonList(item1);
    CompletableResultCode resultCode = storageExporter.exportToStorage(items);

Comment on lines 20 to +35
public interface SignalStorage<T> extends Iterable<Collection<T>>, Closeable {

/**
* Stores signal items.
*
* @param items The items to be stored.
* @return A future with {@link WriteResult}.
* @return A {@link CompletableResultCode} representing the outcome of the write operation.
*/
CompletableFuture<WriteResult> write(Collection<T> items);
CompletableResultCode write(Collection<T> items);

/**
* Removes all the previously stored items.
*
* @return A future with {@link WriteResult}.
* @return A {@link CompletableResultCode} representing the outcome of the clear operation.
*/
CompletableFuture<WriteResult> clear();
CompletableResultCode clear();
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing SignalStorage.write/clear from CompletableFuture<WriteResult> to CompletableResultCode is a breaking change to a public API. If this module follows semver/stability guarantees, consider providing a migration path (e.g., keeping the old methods as deprecated default methods for one release, or documenting the breaking change in the appropriate changelog/release notes) so downstream implementers aren’t broken unexpectedly.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that disk-buffering is still not marked as stable, this is a feasible breaking change. It won't affect users who rely on the provided [Signal]ToDiskExporter implementations from this lib, and even if some users were manually accessing the storage APIs, the CompletableFuture capabilities have a 1:1 replacement in CompletableResultCode, as the latter was created as an alternative for CompletableFuture in mind.

Comment on lines +28 to +32
public CompletableResultCode exportToStorage(Collection<T> items) {
CompletableFuture<WriteResult> future = storage.write(items);
try {
WriteResult operation = future.get(writeTimeout.toMillis(), MILLISECONDS);
if (operation.isSuccessful()) {
callback.onExportSuccess(items);
return CompletableResultCode.ofSuccess();
}
CompletableResultCode result =
storage.write(items).join(writeTimeout.toMillis(), TimeUnit.MILLISECONDS);
if (result.isSuccess()) {
callback.onExportSuccess(items);
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CompletableResultCode.join(timeout, unit) can return even when the result is not completed (see other usages that check isDone() after join). In that case this code will treat an incomplete result as a plain failure with null throwable, which changes the previous behavior where timeouts produced an exceptional failure (and onExportError received the timeout). Consider checking result.isDone() after join(...) and, if false, invoking onExportError with a TimeoutException (and returning CompletableResultCode.ofExceptionalFailure(...)). Also consider preserving/propagating thread interruption if the join returns early due to interrupt.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 5352ca7

Copy link

@bidetofevil bidetofevil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. The return logic in SignalStorageExporter could probably be simplified to improve readability (there are 4 return statements in which only one produces a successful result, but it's sandwiched between 2 blocks that return failures), but that shouldn't block it if you don't have time.

@LikeTheSalad
Copy link
Contributor Author

Looks good to me. The return logic in SignalStorageExporter could probably be simplified to improve readability (there are 4 return statements in which only one produces a successful result, but it's sandwiched between 2 blocks that return failures), but that shouldn't block it if you don't have time.

Thanks. I've updated it to improve readability.

@trask trask added this pull request to the merge queue Mar 4, 2026
Merged via the queue into open-telemetry:main with commit de0902d Mar 4, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants