Skip to content

Commit 0f37684

Browse files
fixing smart retry impl
1 parent a9abd2e commit 0f37684

File tree

8 files changed

+1483
-181
lines changed

8 files changed

+1483
-181
lines changed

sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/specialized/BlobAsyncClientBase.java

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
import com.azure.storage.common.implementation.Constants;
8686
import com.azure.storage.common.implementation.SasImplUtils;
8787
import com.azure.storage.common.implementation.StorageImplUtils;
88+
import com.azure.storage.common.policy.StorageContentValidationDecoderPolicy;
8889
import reactor.core.publisher.Flux;
8990
import reactor.core.publisher.Mono;
9091
import reactor.core.publisher.SignalType;
@@ -1342,24 +1343,65 @@ Mono<BlobDownloadAsyncResponse> downloadStreamWithResponse(BlobRange range, Down
13421343
try {
13431344
// For retry context, preserve decoder state if structured message validation is enabled
13441345
Context retryContext = firstRangeContext;
1346+
BlobRange retryRange;
13451347

1346-
// If structured message decoding is enabled, we need to include the decoder state
1347-
// so the retry can continue from where we left off
1348+
// If structured message decoding is enabled, we need to calculate the retry offset
1349+
// based on the encoded bytes processed, not the decoded bytes
13481350
if (contentValidationOptions != null
13491351
&& contentValidationOptions.isStructuredMessageValidationEnabled()) {
1350-
// The decoder state will be set by the policy during processing
1351-
// We preserve it in the context for the retry request
1352-
Object decoderState
1352+
// Get the decoder state to determine how many encoded bytes were processed
1353+
Object decoderStateObj
13531354
= firstRangeContext.getData(Constants.STRUCTURED_MESSAGE_DECODER_STATE_CONTEXT_KEY)
13541355
.orElse(null);
1355-
if (decoderState != null) {
1356+
1357+
if (decoderStateObj instanceof StorageContentValidationDecoderPolicy.DecoderState) {
1358+
StorageContentValidationDecoderPolicy.DecoderState decoderState
1359+
= (StorageContentValidationDecoderPolicy.DecoderState) decoderStateObj;
1360+
1361+
// Use getRetryOffset() to get the correct offset for retry
1362+
// This accounts for pending bytes that have been received but not yet consumed
1363+
long encodedOffset = decoderState.getRetryOffset();
1364+
long remainingCount = finalCount - encodedOffset;
1365+
retryRange = new BlobRange(initialOffset + encodedOffset, remainingCount);
1366+
1367+
LOGGER.info(
1368+
"Structured message smart retry: resuming from offset {} (initial={}, encoded={})",
1369+
initialOffset + encodedOffset, initialOffset, encodedOffset);
1370+
1371+
// Preserve the decoder state for the retry
13561372
retryContext = retryContext
13571373
.addData(Constants.STRUCTURED_MESSAGE_DECODER_STATE_CONTEXT_KEY, decoderState);
1374+
} else {
1375+
// No decoder state available, try to parse retry offset from exception message
1376+
// The exception message contains RETRY-START-OFFSET=<number> token
1377+
long retryStartOffset = StorageContentValidationDecoderPolicy
1378+
.parseRetryStartOffset(throwable.getMessage());
1379+
if (retryStartOffset >= 0) {
1380+
long remainingCount = finalCount - retryStartOffset;
1381+
// Validate remainingCount to avoid negative values
1382+
if (remainingCount <= 0) {
1383+
LOGGER.warning("Retry offset {} exceeds finalCount {}, using fallback",
1384+
retryStartOffset, finalCount);
1385+
retryRange = new BlobRange(initialOffset + offset, newCount);
1386+
} else {
1387+
retryRange = new BlobRange(initialOffset + retryStartOffset, remainingCount);
1388+
1389+
LOGGER.info(
1390+
"Structured message smart retry from exception: resuming from offset {} "
1391+
+ "(initial={}, parsed={})",
1392+
initialOffset + retryStartOffset, initialOffset, retryStartOffset);
1393+
}
1394+
} else {
1395+
// Fallback to normal retry logic if no offset found
1396+
retryRange = new BlobRange(initialOffset + offset, newCount);
1397+
}
13581398
}
1399+
} else {
1400+
// For non-structured downloads, use smart retry from the interrupted offset
1401+
retryRange = new BlobRange(initialOffset + offset, newCount);
13591402
}
13601403

1361-
return downloadRange(new BlobRange(initialOffset + offset, newCount), finalRequestConditions,
1362-
eTag, finalGetMD5, retryContext);
1404+
return downloadRange(retryRange, finalRequestConditions, eTag, finalGetMD5, retryContext);
13631405
} catch (Exception e) {
13641406
return Mono.error(e);
13651407
}

0 commit comments

Comments
 (0)