Conversation
f7dacda to
ea2e309
Compare
ba8840b to
6d89325
Compare
There was a problem hiding this comment.
Code Review
This pull request implements Category 2 (recoverable) error recovery for resumable uploads in GAX Java, updating ChunkAttemptCallable to query session status, realign the buffer window, and dispatch chunk uploads upon encountering recoverable errors or missing status headers. The review feedback highlights critical improvement opportunities in ChunkAttemptCallable, including addressing a potential infinite loop by correctly classifying query status failures under UploadCommand.QUERY rather than UPLOAD, and preventing race conditions by checking if the retrying future is cancelled immediately after setting inFlightFuture for both query and chunk upload futures.
| public void onFailure(Throwable t) { | ||
| failAttempt(attemptFuture, t); | ||
| } |
There was a problem hiding this comment.
If the query status call fails with a non-transient error (such as 400 Bad Request or 403 Forbidden), classifying it under the chunk's upload command (e.g., UPLOAD) in UploadResultRetryAlgorithm would incorrectly classify it as RECOVERABLE (since 400 is recoverable for UPLOAD). This would trigger another recovery attempt, leading to an infinite loop of failing query status calls. We should classify the query failure using UploadCommand.QUERY and fail the attempt with a non-retryable exception if it is not transient.
@Override
public void onFailure(Throwable t) {
UploadErrorCategory category = UploadErrorClassifier.classify(t, UploadCommand.QUERY);
if (category == UploadErrorCategory.TRANSIENT) {
failAttempt(attemptFuture, t);
} else {
failAttempt(
attemptFuture,
new IllegalStateException("Fatal error during query status: " + t.getMessage(), t));
}
}| attemptFuture, new IllegalStateException("queryStatusCallable returned a null future")); | ||
| return; | ||
| } | ||
| this.inFlightFuture = queryFuture; |
There was a problem hiding this comment.
To prevent a race condition where the retrying future is cancelled concurrently before inFlightFuture is set, we should check if currentRetryingFuture is cancelled immediately after setting inFlightFuture and propagate the cancellation if so.
this.inFlightFuture = queryFuture;
if (currentRetryingFuture.isCancelled()) {
queryFuture.cancel(true);
}|
|
||
| ApiFuture<ChunkUploadResponse<ResponseT>> chunkFuture = | ||
| uploadChunkCallable.futureCall(currentRequest, attemptContext); | ||
| this.inFlightFuture = chunkFuture; |
There was a problem hiding this comment.
To prevent a race condition where the retrying future is cancelled concurrently before inFlightFuture is set, we should check if currentRetryingFuture is cancelled immediately after setting inFlightFuture and propagate the cancellation if so.
this.inFlightFuture = chunkFuture;
if (currentRetryingFuture.isCancelled()) {
chunkFuture.cancel(true);
}ea2e309 to
e197ee8
Compare
6d89325 to
da1275c
Compare
e197ee8 to
12cd1cb
Compare
da1275c to
589ef8e
Compare
12cd1cb to
dd1aaac
Compare
589ef8e to
f130ccc
Compare
dd1aaac to
7fb2335
Compare
f130ccc to
0e94df8
Compare
7fb2335 to
4795631
Compare
0e94df8 to
00f7932
Compare
4795631 to
1fe75dc
Compare
00f7932 to
c6c28b1
Compare
1fe75dc to
8456db3
Compare
c6c28b1 to
4a4b890
Compare
8456db3 to
04da457
Compare
4a4b890 to
14f6204
Compare
04da457 to
f60b76a
Compare
14f6204 to
0a389c4
Compare
f60b76a to
d5c48d6
Compare
0a389c4 to
7d192ff
Compare
d5c48d6 to
599ab38
Compare
7d192ff to
33b9862
Compare
Introduces ChunkAttemptCallable with a query-and-realign loop when encountering recoverable protocol errors. Queries the server for the committed offset and adjusts the buffer window before resuming chunk transmission.
33b9862 to
12f0b82
Compare
599ab38 to
292921f
Compare
|
|




Introduces
ChunkAttemptCallablewith a query-and-realign loop when encountering recoverable protocol errors. Queries the server for the committed offset and adjusts the buffer window before resuming chunk transmission.