Skip to content

refactor(gax): remove circular ref between resumable upload future and chunk coordinator - #14421

Draft
whowes wants to merge 1 commit into
whowes/resumable-upload-status-headerfrom
whowes/resumable-upload-coordinator-refactor
Draft

whowes wants to merge 1 commit into
whowes/resumable-upload-status-headerfrom
whowes/resumable-upload-coordinator-refactor

Conversation

@whowes

@whowes whowes commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

This cleans up and helps clarify the layering and responsibility structure ahead of introducing non-happy path features.

In general the Future coordinates the overall upload lifecycle while delegating details of specific operations (e.g. chunk uploads, status listeners, global timeout) to the layer below. Actors on that layer don't maintain explicit references to the Future.

@whowes
whowes force-pushed the whowes/resumable-upload-coordinator-refactor branch from 540dbd9 to 8f03a72 Compare September 17, 2026 22:08
gemini-code-assist[bot]

This comment was marked as outdated.

@whowes
whowes added this pull request to stack #14429 September 17, 2026 22:16
@whowes
whowes force-pushed the whowes/resumable-upload-coordinator-refactor branch from 8f03a72 to f4bc308 Compare September 18, 2026 02:23
@whowes
whowes force-pushed the whowes/resumable-upload-coordinator-refactor branch from f4bc308 to 3979d5c Compare September 18, 2026 03:21
@whowes
whowes force-pushed the whowes/resumable-upload-coordinator-refactor branch from 3979d5c to dcd33e6 Compare September 18, 2026 15:04
@whowes
whowes force-pushed the whowes/resumable-upload-coordinator-refactor branch from dcd33e6 to 572db5b Compare September 19, 2026 01:36
@whowes
whowes force-pushed the whowes/resumable-upload-coordinator-refactor branch 3 times, most recently from d885bb2 to f2cc9b8 Compare September 20, 2026 00:19
@whowes whowes changed the title refactor(gax): invert resumable upload future and coordinator ownership refactor(gax): remove circular dep between resumable upload future and chunk coordinator Sep 20, 2026
@whowes
whowes removed this pull request from stack #14429 September 20, 2026 07:20
@whowes
whowes added this pull request to stack #14454 September 20, 2026 07:21
@whowes
whowes force-pushed the whowes/resumable-upload-coordinator-refactor branch from f2cc9b8 to 778fac8 Compare September 20, 2026 07:45
@whowes

whowes commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the resumable upload coordination by decoupling ResumableUploadChunkCoordinator from ResumableUploadFutureImpl. The coordinator now manages its own internal state and returns an ApiFuture upon starting, while ResumableUploadFutureImpl has been updated to observe this future and has removed the setInFlightFuture method. However, the review identifies two critical race conditions introduced by this refactoring: one in ResumableUploadChunkCoordinator where a cancellation of the result future after the initial check but before assigning currentChunkFuture can leak a background upload task, and another in ResumableUploadFutureImpl where a cancellation during coordinator.start() is not properly propagated to the newly created upload future. Both issues require adding explicit cancellation checks and propagation logic to prevent background resource leaks.

ApiFuture<ChunkUploadResponse<ResponseT>> chunkFuture =
uploadChunkCallable.futureCall(chunkRequest, callContext);
sessionFuture.setInFlightFuture(chunkFuture);
this.currentChunkFuture = chunkFuture;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

There is a race condition where result can be cancelled after result.isDone() is checked at the beginning of transmitChunk, but before currentChunkFuture is assigned. In this scenario, the cancellation listener registered in start() will have already executed (finding currentChunkFuture to be null or a previous chunk), and the newly created chunkFuture will never be cancelled, leading to a leaked background upload task.

To prevent this, check if result has been cancelled immediately after assigning currentChunkFuture and cancel the chunk future if so.

      this.currentChunkFuture = chunkFuture;
      if (result.isCancelled()) {
        chunkFuture.cancel(true);
      }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Comment on lines +142 to 147
synchronized (lock) {
if (resultFuture.isDone()) {
return;
}
inFlightFuture = uploadFuture;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

With the removal of setInFlightFuture, there is a race condition where resultFuture can be cancelled while coordinator.start() is executing. If this happens, resultFuture.isDone() will be true when entering the synchronized block, and the method will return early without cancelling the newly started uploadFuture. This can leak the upload process in the background.

To fix this, check if resultFuture was cancelled when it is done, and propagate the cancellation to uploadFuture accordingly.

            boolean shouldCancel = false;
            synchronized (lock) {
              if (resultFuture.isDone()) {
                shouldCancel = resultFuture.isCancelled();
              } else {
                inFlightFuture = uploadFuture;
              }
            }
            if (shouldCancel) {
              uploadFuture.cancel(true);
              return;
            }
References
  1. When concurrent operations (such as cancellation and lazy initialization) are protected by a common lock (e.g., synchronized (this)), atomic state transitions (like compareAndSet) are not strictly necessary as the synchronization already prevents concurrent interleaving.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@whowes
whowes force-pushed the whowes/resumable-upload-coordinator-refactor branch from 778fac8 to 5f27eaa Compare September 21, 2026 18:32
@whowes
whowes force-pushed the whowes/resumable-upload-coordinator-refactor branch from 5f27eaa to 4459004 Compare September 21, 2026 20:25
@whowes whowes changed the title refactor(gax): remove circular dep between resumable upload future and chunk coordinator refactor(gax): remove circular ref between resumable upload future and chunk coordinator Sep 21, 2026
@whowes
whowes force-pushed the whowes/resumable-upload-coordinator-refactor branch from 4459004 to 8fa30bb Compare September 21, 2026 22:16
@whowes
whowes force-pushed the whowes/resumable-upload-coordinator-refactor branch from 8fa30bb to 01efb2f Compare September 21, 2026 22:34
@whowes
whowes force-pushed the whowes/resumable-upload-coordinator-refactor branch from 01efb2f to 5262566 Compare September 21, 2026 22:59
@whowes
whowes force-pushed the whowes/resumable-upload-coordinator-refactor branch from 5262566 to 291aebe Compare September 22, 2026 06:46
@whowes
whowes force-pushed the whowes/resumable-upload-coordinator-refactor branch from 291aebe to b0a46d3 Compare September 22, 2026 07:03
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed for 'gapic-generator-java-root'

Failed conditions
76.1% Coverage on New Code (required ≥ 80%)
B Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed for 'gapic-generator-java-root'

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)
B Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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.

1 participant