Skip to content

Commit 6c1869f

Browse files
committed
fix: cancel upload when BlobWriter exits with exception
1 parent 1c7caca commit 6c1869f

File tree

4 files changed

+60
-1
lines changed

4 files changed

+60
-1
lines changed

README.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ setup.py file. Applications which do not import directly from
7272
`google-resumable-media` can safely disregard this dependency. This backwards
7373
compatibility feature will be removed in a future major version update.
7474

75+
Miscellaneous
76+
~~~~~~~~~~~~~
77+
78+
- The BlobWriter class now attempts to terminate an ongoing resumable upload if
79+
the writer exits with an exception.
80+
7581
Quick Start
7682
-----------
7783

docs/storage/exceptions.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Exceptions
2-
~~~~~~~~~
2+
~~~~~~~~~~
33

44
.. automodule:: google.cloud.storage.exceptions
55
:members:

google/cloud/storage/fileio.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,19 @@ def close(self):
437437
self._upload_chunks_from_buffer(1)
438438
self._buffer.close()
439439

440+
def terminate(self):
441+
"""Cancel the ResumableUpload."""
442+
if self._upload_and_transport:
443+
upload, transport = self._upload_and_transport
444+
transport.delete(upload.upload_url)
445+
self._buffer.close()
446+
447+
def __exit__(self, exc_type, exc_val, exc_tb):
448+
if exc_type is not None:
449+
self.terminate()
450+
else:
451+
self.close()
452+
440453
@property
441454
def closed(self):
442455
return self._buffer.closed

tests/system/test_fileio.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
# limitations under the License.
1515

1616

17+
import pytest
18+
1719
from .test_blob import _check_blob_hash
1820

1921

@@ -76,3 +78,41 @@ def test_blobwriter_and_blobreader_text_mode(
7678
assert text_data[:100] == reader.read(100)
7779
assert 0 == reader.seek(0)
7880
assert reader.read() == text_data
81+
82+
83+
84+
def test_blobwriter_exit(
85+
shared_bucket,
86+
blobs_to_delete,
87+
service_account,
88+
):
89+
blob = shared_bucket.blob("NeverUploaded")
90+
91+
# no-op when nothing was uploaded yet
92+
with pytest.raises(ValueError, match="SIGTERM received"):
93+
with blob.open("wb") as writer:
94+
writer.write(b"first chunk") # not yet uploaded
95+
raise ValueError("SIGTERM received") # no upload to cancel in __exit__
96+
# blob should not exist
97+
assert not blob.exists()
98+
99+
# unhandled exceptions should cancel the upload
100+
with pytest.raises(ValueError, match="SIGTERM received"):
101+
with blob.open("wb") as writer:
102+
writer.write(b"first chunk") # not yet uploaded
103+
writer.write(b"big chunk" * 1024 ** 8) # uploaded
104+
raise ValueError("SIGTERM received") # upload is cancelled in __exit__
105+
# blob should not exist
106+
assert not blob.exists()
107+
108+
# handled exceptions should not cancel the upload
109+
with blob.open("wb") as writer:
110+
writer.write(b"first chunk") # not yet uploaded
111+
writer.write(b"big chunk" * 1024 ** 8) # uploaded
112+
try:
113+
raise ValueError("This is fine")
114+
except ValueError:
115+
pass # no exception context passed to __exit__
116+
blobs_to_delete.append(blob)
117+
# blob should have been uploaded
118+
assert blob.exists()

0 commit comments

Comments
 (0)