3535import com .google .api .core .ApiFutureCallback ;
3636import com .google .api .core .ApiFutures ;
3737import com .google .api .core .InternalApi ;
38+ import com .google .api .core .SettableApiFuture ;
3839import com .google .api .gax .resumable .ChunkUploadRequest ;
3940import com .google .api .gax .resumable .ChunkUploadResponse ;
4041import com .google .api .gax .resumable .ResumableUploadStatus ;
4546import java .util .Arrays ;
4647import java .util .concurrent .CancellationException ;
4748import org .jspecify .annotations .NullMarked ;
49+ import org .jspecify .annotations .Nullable ;
4850
4951/**
5052 * Coordinates chunk transmission steps of a resumable upload session.
@@ -64,32 +66,40 @@ final class ResumableUploadChunkCoordinator<ResponseT> {
6466 private final byte [] buffer ;
6567 private final int chunkSize ;
6668 private final ApiCallContext callContext ;
67- private final ResumableUploadFutureImpl <ResponseT > sessionFuture ;
69+ private final SettableApiFuture <ResponseT > result = SettableApiFuture .create ();
70+ private volatile @ Nullable ApiFuture <?> currentChunkFuture ;
6871
6972 ResumableUploadChunkCoordinator (
7073 UnaryCallable <ChunkUploadRequest , ChunkUploadResponse <ResponseT >> uploadChunkCallable ,
7174 String uploadUrl ,
7275 InputStream payload ,
7376 int chunkSize ,
74- ApiCallContext callContext ,
75- ResumableUploadFutureImpl <ResponseT > sessionFuture ) {
77+ ApiCallContext callContext ) {
7678 this .uploadChunkCallable =
7779 checkNotNull (uploadChunkCallable , "uploadChunkCallable must not be null" );
7880 this .uploadUrl = checkNotNull (uploadUrl , "uploadUrl must not be null" );
7981 this .payload = checkNotNull (payload , "payload must not be null" );
8082 this .chunkSize = chunkSize ;
8183 this .callContext = checkNotNull (callContext , "callContext must not be null" );
82- this .sessionFuture = checkNotNull (sessionFuture , "sessionFuture must not be null" );
8384 this .buffer = new byte [chunkSize ];
8485 }
8586
86- void start () {
87+ ApiFuture <ResponseT > start () {
88+ result .addListener (
89+ () -> {
90+ ApiFuture <?> chunk = currentChunkFuture ;
91+ if (result .isCancelled () && chunk != null ) {
92+ chunk .cancel (true );
93+ }
94+ },
95+ MoreExecutors .directExecutor ());
8796 transmitChunk (0L );
97+ return result ;
8898 }
8999
90100 private void transmitChunk (long currentOffset ) {
91101 // Abort if the session was already completed or canceled.
92- if (sessionFuture .isDone ()) {
102+ if (result .isDone ()) {
93103 return ;
94104 }
95105
@@ -98,7 +108,7 @@ private void transmitChunk(long currentOffset) {
98108 try {
99109 bytesRead = ByteStreams .read (payload , buffer , 0 , chunkSize );
100110 } catch (IOException e ) {
101- sessionFuture . fail (e );
111+ result . setException (e );
102112 return ;
103113 }
104114
@@ -126,22 +136,21 @@ private void transmitChunk(long currentOffset) {
126136 try {
127137 ApiFuture <ChunkUploadResponse <ResponseT >> chunkFuture =
128138 uploadChunkCallable .futureCall (chunkRequest , callContext );
129- sessionFuture . setInFlightFuture ( chunkFuture ) ;
139+ this . currentChunkFuture = chunkFuture ;
130140
131- // Asynchronously handle the response: complete, fail, or chain the next chunk.
132141 ApiFutures .addCallback (
133142 chunkFuture ,
134143 new ApiFutureCallback <ChunkUploadResponse <ResponseT >>() {
135144 @ Override
136145 public void onSuccess (ChunkUploadResponse <ResponseT > response ) {
137- if (sessionFuture .isDone ()) {
146+ if (result .isDone ()) {
138147 return ;
139148 }
140149 long nextOffset = currentOffset + chunkLength ;
141150 if (response .getUploadStatus () == ResumableUploadStatus .FINAL ) {
142- sessionFuture . succeed (response .getResponse ());
151+ result . set (response .getResponse ());
143152 } else if (isFinal ) {
144- sessionFuture . fail (
153+ result . setException (
145154 new IllegalStateException (
146155 "Upload stream ended and final chunk was transmitted, but server returned"
147156 + " incomplete status" ));
@@ -152,15 +161,15 @@ public void onSuccess(ChunkUploadResponse<ResponseT> response) {
152161
153162 @ Override
154163 public void onFailure (Throwable t ) {
155- if (t instanceof CancellationException || sessionFuture .isDone ()) {
164+ if (t instanceof CancellationException || result .isDone ()) {
156165 return ;
157166 }
158- sessionFuture . fail (t );
167+ result . setException (t );
159168 }
160169 },
161170 MoreExecutors .directExecutor ());
162171 } catch (Throwable t ) {
163- sessionFuture . fail (t );
172+ result . setException (t );
164173 }
165174 }
166175}
0 commit comments