@@ -78,6 +78,7 @@ final class ResumableUploadChunkCoordinator<ResponseT> {
7878 private final long deadlineNanos ;
7979 private final ApiCallContext callContext ;
8080 private final ClientContext clientContext ;
81+ private final UploadProgressTracker progressTracker ;
8182 private final SettableApiFuture <ResponseT > result = SettableApiFuture .create ();
8283 private volatile @ Nullable ApiFuture <?> currentChunkFuture ;
8384
@@ -87,7 +88,8 @@ final class ResumableUploadChunkCoordinator<ResponseT> {
8788 String uploadUrl ,
8889 ResumableUploadCallSettings settings ,
8990 long deadlineNanos ,
90- ClientContext clientContext ) {
91+ ClientContext clientContext ,
92+ UploadProgressTracker progressTracker ) {
9193 this .uploadChunkCallable =
9294 checkNotNull (uploadChunkCallable , "uploadChunkCallable must not be null" );
9395 this .queryStatusCallable =
@@ -98,6 +100,7 @@ final class ResumableUploadChunkCoordinator<ResponseT> {
98100 this .deadlineNanos = deadlineNanos ;
99101 this .clientContext = checkNotNull (clientContext , "clientContext must not be null" );
100102 this .callContext = clientContext .getDefaultCallContext ();
103+ this .progressTracker = checkNotNull (progressTracker , "progressTracker must not be null" );
101104 this .buffer = new RewindableStreamBuffer (payload , settings .getChunkSize (), uploadUrl );
102105 }
103106
@@ -145,7 +148,7 @@ private void dispatchCurrentChunk() {
145148 @ Override
146149 public void onSuccess (ChunkUploadResponse <ResponseT > response ) {
147150 if (response .getUploadStatus () == ResumableUploadStatus .UNKNOWN ) {
148- recover ();
151+ recover (null );
149152 } else {
150153 onChunkUploaded (response );
151154 }
@@ -159,7 +162,7 @@ public void onFailure(Throwable t) {
159162 Category category =
160163 ResumableUploadErrorClassifier .classify (t , ResumableUploadCommand .UPLOAD );
161164 if (category == Category .RECOVERABLE ) {
162- recover ();
165+ recover (t );
163166 } else {
164167 // Category.TRANSIENT errors reaching here have already exhausted their retry budget
165168 // in the underlying RetryingCallable and become fatal per protocol specification.
@@ -173,10 +176,17 @@ public void onFailure(Throwable t) {
173176 }
174177 }
175178
176- private void recover () {
179+ /**
180+ * Queries the session for the server's committed offset and resumes transmission from it.
181+ *
182+ * @param cause the error that triggered recovery, or null if the server acknowledged the chunk
183+ * without an upload status header
184+ */
185+ private void recover (@ Nullable Throwable cause ) {
177186 if (result .isDone ()) {
178187 return ;
179188 }
189+ progressTracker .onRecovering (cause );
180190 try {
181191 ApiFuture <QueryStatusResponse <ResponseT >> queryFuture =
182192 queryStatusCallable .futureCall (QueryStatusRequest .create (uploadUrl ), queryCallContext ());
@@ -230,6 +240,7 @@ private void handleQueryResponse(QueryStatusResponse<ResponseT> queryResponse) t
230240 "Incomplete query status response did not include a committed offset for upload URL: "
231241 + uploadUrl );
232242 }
243+ progressTracker .onOffsetReceived (committedOffset );
233244 buffer .realignTo (committedOffset );
234245 dispatchCurrentChunk ();
235246 }
@@ -238,17 +249,23 @@ private void onChunkUploaded(ChunkUploadResponse<ResponseT> response) {
238249 if (result .isDone ()) {
239250 return ;
240251 }
241- long nextOffset = buffer .getBufferBaseOffset () + buffer .getPayloadLength ();
242- if (response .getUploadStatus () == ResumableUploadStatus .FINAL ) {
243- result .set (response .getResponse ());
244- } else if (buffer .isFinal ()) {
245- result .setException (
246- new IllegalStateException (
247- "Upload stream ended and final chunk was transmitted, but server returned"
248- + " incomplete status for upload URL: "
249- + uploadUrl ));
250- } else {
251- chunkExecutor .execute (() -> transmitChunk (nextOffset ));
252+ try {
253+ long nextOffset = buffer .getBufferBaseOffset () + buffer .getPayloadLength ();
254+ if (response .getUploadStatus () == ResumableUploadStatus .FINAL ) {
255+ progressTracker .onChunkUploaded (nextOffset );
256+ result .set (response .getResponse ());
257+ } else if (buffer .isFinal ()) {
258+ result .setException (
259+ new IllegalStateException (
260+ "Upload stream ended and final chunk was transmitted, but server returned"
261+ + " incomplete status for upload URL: "
262+ + uploadUrl ));
263+ } else {
264+ progressTracker .onChunkUploaded (nextOffset );
265+ chunkExecutor .execute (() -> transmitChunk (nextOffset ));
266+ }
267+ } catch (Throwable t ) {
268+ result .setException (t );
252269 }
253270 }
254271
0 commit comments