Skip to content

Commit 04d3960

Browse files
Fix smart retry for structured message validation to restart from beginning
The issue was that structured messages cannot be decoded from arbitrary offsets - they must be decoded sequentially from the start. When a network interruption occurs during a download with structured message validation, the retry must restart from offset 0, not from the interrupted offset. Changes to BlobAsyncClientBase: - Modified retry logic to check if structured message validation is enabled - When enabled, retries restart from the initial offset (beginning of download) - When disabled, normal smart retry behavior resumes from interrupted offset - This fixes "Unexpected segment number" errors that occurred when trying to decode from middle offsets Changes to tests: - Updated test assertions to verify all requests start from offset 0 with structured message validation - Updated test comments to reflect correct behavior (retries restart from beginning) - Tests now properly validate that structured message downloads succeed despite interruptions Co-authored-by: gunjansingh-msft <[email protected]>
1 parent 4dd559e commit 04d3960

File tree

2 files changed

+42
-27
lines changed

2 files changed

+42
-27
lines changed

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

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,26 +1389,26 @@ Mono<BlobDownloadAsyncResponse> downloadStreamWithResponse(BlobRange range, Down
13891389
}
13901390

13911391
try {
1392-
// For retry context, preserve decoder state if structured message validation is enabled
1392+
// For retry context, determine the retry behavior based on validation options
13931393
Context retryContext = firstRangeContext;
1394+
BlobRange retryRange;
13941395

1395-
// If structured message decoding is enabled, we need to include the decoder state
1396-
// so the retry can continue from where we left off
1396+
// If structured message decoding is enabled, we must restart from the beginning
1397+
// because structured messages cannot be decoded from arbitrary offsets
13971398
if (contentValidationOptions != null
13981399
&& contentValidationOptions.isStructuredMessageValidationEnabled()) {
1399-
// The decoder state will be set by the policy during processing
1400-
// We preserve it in the context for the retry request
1401-
Object decoderState
1402-
= firstRangeContext.getData(Constants.STRUCTURED_MESSAGE_DECODER_STATE_CONTEXT_KEY)
1403-
.orElse(null);
1404-
if (decoderState != null) {
1405-
retryContext = retryContext
1406-
.addData(Constants.STRUCTURED_MESSAGE_DECODER_STATE_CONTEXT_KEY, decoderState);
1407-
}
1400+
// Structured messages require sequential decoding from the start.
1401+
// We cannot resume from middle, so restart the entire download.
1402+
// Clear the decoder state to start fresh.
1403+
retryContext
1404+
= retryContext.addData(Constants.STRUCTURED_MESSAGE_DECODER_STATE_CONTEXT_KEY, null);
1405+
retryRange = new BlobRange(initialOffset, finalCount);
1406+
} else {
1407+
// For non-structured downloads, use smart retry from the interrupted offset
1408+
retryRange = new BlobRange(initialOffset + offset, newCount);
14081409
}
14091410

1410-
return downloadRange(new BlobRange(initialOffset + offset, newCount), finalRequestConditions,
1411-
eTag, finalGetMD5, retryContext);
1411+
return downloadRange(retryRange, finalRequestConditions, eTag, finalGetMD5, retryContext);
14121412
} catch (Exception e) {
14131413
return Mono.error(e);
14141414
}

sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/specialized/BlobMessageDecoderDownloadTests.java

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,10 @@ public void downloadStreamWithResponseContentValidationVeryLargeBlob() throws IO
227227
public void downloadStreamWithResponseContentValidationSmartRetry() throws IOException {
228228
// Test smart retry functionality with structured message validation
229229
// This test simulates network interruptions and verifies that:
230-
// 1. The decoder validates checksums for all received data before retry
231-
// 2. The decoder state is preserved across retries
232-
// 3. The SDK continues from the correct offset after interruption
230+
// 1. The decoder validates checksums for all received data
231+
// 2. Retries restart from the beginning (offset 0) since structured messages
232+
// cannot be decoded from arbitrary offsets
233+
// 3. The download eventually succeeds despite multiple interruptions
233234

234235
byte[] randomData = getRandomByteArray(Constants.KB);
235236
StructuredMessageEncoder encoder
@@ -272,21 +273,20 @@ public void downloadStreamWithResponseContentValidationSmartRetry() throws IOExc
272273
List<String> rangeHeaders = mockPolicy.getRangeHeaders();
273274
assertTrue(rangeHeaders.size() > 0, "Expected range headers for retries");
274275

275-
// The first request should be for the entire blob
276-
assertTrue(rangeHeaders.get(0).startsWith("bytes=0-"), "First request should start from offset 0");
277-
278-
// Subsequent requests should continue from where we left off (after the first byte)
279-
for (int i = 1; i < rangeHeaders.size(); i++) {
276+
// With structured message validation, all requests (including retries) must start from offset 0
277+
// because structured messages cannot be decoded from arbitrary offsets
278+
for (int i = 0; i < rangeHeaders.size(); i++) {
280279
String rangeHeader = rangeHeaders.get(i);
281-
assertTrue(rangeHeader.startsWith("bytes=" + i + "-"),
282-
"Retry request " + i + " should start from offset " + i + " but was: " + rangeHeader);
280+
assertTrue(rangeHeader.startsWith("bytes=0-"), "Request " + i
281+
+ " should start from offset 0 for structured message validation, but was: " + rangeHeader);
283282
}
284283
}
285284

286285
@Test
287286
public void downloadStreamWithResponseContentValidationSmartRetryMultipleSegments() throws IOException {
288287
// Test smart retry with multiple segments to ensure checksum validation
289-
// works correctly across segment boundaries during network interruptions
288+
// works correctly across segment boundaries. Retries restart from the beginning
289+
// to properly validate all segments sequentially.
290290

291291
byte[] randomData = getRandomByteArray(2 * Constants.KB);
292292
StructuredMessageEncoder encoder
@@ -328,12 +328,19 @@ public void downloadStreamWithResponseContentValidationSmartRetryMultipleSegment
328328
List<String> rangeHeaders = mockPolicy.getRangeHeaders();
329329
assertTrue(rangeHeaders.size() >= 4,
330330
"Expected at least 4 range headers for retries, got: " + rangeHeaders.size());
331+
332+
// With structured message validation, all requests must start from offset 0
333+
for (int i = 0; i < rangeHeaders.size(); i++) {
334+
String rangeHeader = rangeHeaders.get(i);
335+
assertTrue(rangeHeader.startsWith("bytes=0-"), "Request " + i
336+
+ " should start from offset 0 for structured message validation, but was: " + rangeHeader);
337+
}
331338
}
332339

333340
@Test
334341
public void downloadStreamWithResponseContentValidationSmartRetryLargeBlob() throws IOException {
335-
// Test smart retry with a larger blob to ensure decoder state
336-
// is correctly maintained across retries with more data
342+
// Test smart retry with a larger blob to ensure retries restart from
343+
// the beginning and successfully validate all data
337344

338345
byte[] randomData = getRandomByteArray(5 * Constants.KB);
339346
StructuredMessageEncoder encoder
@@ -370,5 +377,13 @@ public void downloadStreamWithResponseContentValidationSmartRetryLargeBlob() thr
370377

371378
// Verify that retries occurred
372379
assertEquals(0, mockPolicy.getTriesRemaining());
380+
381+
// Verify that all requests start from offset 0 with structured message validation
382+
List<String> rangeHeaders = mockPolicy.getRangeHeaders();
383+
for (int i = 0; i < rangeHeaders.size(); i++) {
384+
String rangeHeader = rangeHeaders.get(i);
385+
assertTrue(rangeHeader.startsWith("bytes=0-"), "Request " + i
386+
+ " should start from offset 0 for structured message validation, but was: " + rangeHeader);
387+
}
373388
}
374389
}

0 commit comments

Comments
 (0)