Skip to content

Commit 44f49e0

Browse files
authored
fix: last chunk is retriable (#677)
Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [x] Ensure the tests and linter pass - [x] Appropriate docs were updated (if necessary) Fixes #666 ☕️
1 parent ebb5fb2 commit 44f49e0

File tree

3 files changed

+76
-2
lines changed

3 files changed

+76
-2
lines changed

google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannel.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@ public void run() {
120120
// For completeness, this case is not possible because it would require retrying
121121
// a 400 status code which is not allowed.
122122
//
123+
// Case 7: remoteNextByteOffset==-1 && last == true
124+
// Upload is complete and retry occurred in the "last" chunk. Data sent was
125+
// received by the service.
126+
//
127+
// Case 8: remoteNextByteOffset==-1 && last == false
128+
// Upload was completed by another client because this retry did not occur
129+
// during the last chunk.
130+
//
123131
// Get remote offset from API
124132
long remoteNextByteOffset =
125133
getOptions().getStorageRpcV1().getCurrentUploadOffset(getUploadId());
@@ -154,7 +162,8 @@ && driftOffset < getChunkSize()) {
154162
// Continue to next chunk
155163
retrying = false;
156164
return;
157-
} else {
165+
} else if (localNextByteOffset < remoteNextByteOffset
166+
&& driftOffset > getChunkSize()) {
158167
// Case 5
159168
StringBuilder sb = new StringBuilder();
160169
sb.append(
@@ -167,6 +176,13 @@ && driftOffset < getChunkSize()) {
167176
sb.append("remoteNextByteOffset: ").append(remoteNextByteOffset).append('\n');
168177
sb.append("driftOffset: ").append(driftOffset).append("\n\n");
169178
throw new StorageException(0, sb.toString());
179+
} else if (remoteNextByteOffset == -1 && last) {
180+
// Case 7
181+
retrying = false;
182+
return;
183+
} else if (remoteNextByteOffset == -1 && !last) {
184+
// Case 8
185+
throw new StorageException(0, "Resumable upload is already complete.");
170186
}
171187
}
172188
// Request was successful and retrying state is now disabled.

google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,7 @@ public long getCurrentUploadOffset(String uploadId) {
766766
response = httpRequest.execute();
767767
int code = response.getStatusCode();
768768
if (code == 201 || code == 200) {
769-
throw new StorageException(0, "Resumable upload is already complete.");
769+
return -1;
770770
}
771771
StringBuilder sb = new StringBuilder();
772772
sb.append("Not sure what occurred. Here's debugging information:\n");

google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,64 @@ public void testWriteWithFlushRetryChunkWithDrift() throws IOException {
202202
assertArrayEquals(buffer.array(), capturedBuffer.getValue());
203203
}
204204

205+
@Test
206+
public void testWriteWithLastFlushRetryChunkButCompleted() throws IOException {
207+
StorageException exception = new StorageException(new SocketException("Socket closed"));
208+
ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
209+
Capture<byte[]> capturedBuffer = Capture.newInstance();
210+
expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
211+
expect(
212+
storageRpcMock.writeWithResponse(
213+
eq(UPLOAD_ID),
214+
capture(capturedBuffer),
215+
eq(0),
216+
eq(0L),
217+
eq(MIN_CHUNK_SIZE),
218+
eq(true)))
219+
.andThrow(exception);
220+
expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(-1L);
221+
replay(storageRpcMock);
222+
writer = new BlobWriteChannel(options, BLOB_INFO, EMPTY_RPC_OPTIONS);
223+
assertEquals(MIN_CHUNK_SIZE, writer.write(buffer));
224+
writer.close();
225+
assertFalse(writer.isRetrying());
226+
assertFalse(writer.isOpen());
227+
// Capture captures entire buffer of a chunk even when not completely used.
228+
// Making assert selective up to the size of MIN_CHUNK_SIZE
229+
assertArrayEquals(Arrays.copyOf(capturedBuffer.getValue(), MIN_CHUNK_SIZE), buffer.array());
230+
}
231+
232+
@Test
233+
public void testWriteWithFlushRetryChunkButCompletedByAnotherClient() throws IOException {
234+
StorageException exception = new StorageException(new SocketException("Socket closed"));
235+
StorageException completedException =
236+
new StorageException(0, "Resumable upload is already complete.");
237+
ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE);
238+
Capture<byte[]> capturedBuffer = Capture.newInstance();
239+
expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);
240+
expect(
241+
storageRpcMock.writeWithResponse(
242+
eq(UPLOAD_ID),
243+
capture(capturedBuffer),
244+
eq(0),
245+
eq(0L),
246+
eq(MIN_CHUNK_SIZE),
247+
eq(false)))
248+
.andThrow(exception);
249+
expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(-1L);
250+
replay(storageRpcMock);
251+
writer = new BlobWriteChannel(options, BLOB_INFO, EMPTY_RPC_OPTIONS);
252+
writer.setChunkSize(MIN_CHUNK_SIZE);
253+
try {
254+
writer.write(buffer);
255+
fail("Expected completed exception.");
256+
} catch (StorageException ex) {
257+
assertEquals(ex, completedException);
258+
}
259+
assertTrue(writer.isRetrying());
260+
assertTrue(writer.isOpen());
261+
}
262+
205263
@Test
206264
public void testWriteWithFlush() throws IOException {
207265
expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS)).andReturn(UPLOAD_ID);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy