Uh oh!
There was an error while loading. Please reload this page.
This repository was archived by the owner on Mar 31, 2026. It is now read-only.
- Notifications
You must be signed in to change notification settings - Fork 173
feat(experimental): flush the last chunk in append method#1699
Merged
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
38 changes: 30 additions & 8 deletions
38 google/cloud/storage/_experimental/asyncio/async_appendable_object_writer.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 68 additions & 17 deletions
85 tests/unit/asyncio/test_async_appendable_object_writer.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -364,6 +364,7 @@ async def test_close(mock_write_object_stream, mock_client): | ||
| writer = AsyncAppendableObjectWriter(mock_client, BUCKET, OBJECT) | ||
| writer._is_stream_open = True | ||
| writer.offset = 1024 | ||
| writer.persisted_size = 1024 | ||
| mock_stream = mock_write_object_stream.return_value | ||
| mock_stream.send = mock.AsyncMock() | ||
| mock_stream.recv = mock.AsyncMock( | ||
| @@ -435,16 +436,20 @@ async def test_finalize(mock_write_object_stream, mock_client): | ||
| mock_stream.recv = mock.AsyncMock( | ||
| return_value=_storage_v2.BidiWriteObjectResponse(resource=mock_resource) | ||
| ) | ||
| mock_stream.close = mock.AsyncMock() | ||
| gcs_object = await writer.finalize() | ||
| mock_stream.send.assert_awaited_once_with( | ||
| _storage_v2.BidiWriteObjectRequest(finish_write=True) | ||
| ) | ||
| mock_stream.recv.assert_awaited_once() | ||
| mock_stream.close.assert_awaited_once() | ||
chandra-siri marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| assert writer.object_resource == mock_resource | ||
| assert writer.persisted_size == 123 | ||
| assert gcs_object == mock_resource | ||
| assert writer._is_stream_open is False | ||
| assert writer.offset is None | ||
| @pytest.mark.asyncio | ||
| @@ -501,30 +506,39 @@ async def test_append_sends_data_in_chunks(mock_write_object_stream, mock_client | ||
| writer.persisted_size = 100 | ||
| mock_stream = mock_write_object_stream.return_value | ||
| mock_stream.send = mock.AsyncMock() | ||
| writer.simple_flush = mock.AsyncMock() | ||
| data = b"a" * (_MAX_CHUNK_SIZE_BYTES + 1) | ||
| mock_stream.recv = mock.AsyncMock( | ||
| return_value=_storage_v2.BidiWriteObjectResponse( | ||
| persisted_size=100 + len(data) | ||
| ) | ||
| ) | ||
| await writer.append(data) | ||
| assert mock_stream.send.await_count == 2 | ||
| first_call = mock_stream.send.await_args_list[0] | ||
| second_call = mock_stream.send.await_args_list[1] | ||
| first_request = mock_stream.send.await_args_list[0].args[0] | ||
| second_request = mock_stream.send.await_args_list[1].args[0] | ||
| # First chunk | ||
| assert first_call[0][0].write_offset == 100 | ||
| assert len(first_call[0][0].checksummed_data.content) == _MAX_CHUNK_SIZE_BYTES | ||
| assert first_call[0][0].checksummed_data.crc32c == int.from_bytes( | ||
| assert first_request.write_offset == 100 | ||
| assert len(first_request.checksummed_data.content) == _MAX_CHUNK_SIZE_BYTES | ||
| assert first_request.checksummed_data.crc32c == int.from_bytes( | ||
| Checksum(data[:_MAX_CHUNK_SIZE_BYTES]).digest(), byteorder="big" | ||
| ) | ||
| # Second chunk | ||
| assert second_call[0][0].write_offset == 100 + _MAX_CHUNK_SIZE_BYTES | ||
| assert len(second_call[0][0].checksummed_data.content) == 1 | ||
| assert second_call[0][0].checksummed_data.crc32c == int.from_bytes( | ||
| assert not first_request.flush | ||
| assert not first_request.state_lookup | ||
| # Second chunk (last chunk) | ||
| assert second_request.write_offset == 100 + _MAX_CHUNK_SIZE_BYTES | ||
| assert len(second_request.checksummed_data.content) == 1 | ||
| assert second_request.checksummed_data.crc32c == int.from_bytes( | ||
| Checksum(data[_MAX_CHUNK_SIZE_BYTES:]).digest(), byteorder="big" | ||
| ) | ||
| assert second_request.flush | ||
| assert second_request.state_lookup | ||
| assert writer.offset == 100 + len(data) | ||
| writer.simple_flush.assert_not_awaited() | ||
| @pytest.mark.asyncio | ||
| @@ -541,12 +555,25 @@ async def test_append_flushes_when_buffer_is_full( | ||
| writer.persisted_size = 0 | ||
| mock_stream = mock_write_object_stream.return_value | ||
| mock_stream.send = mock.AsyncMock() | ||
| writer.simple_flush = mock.AsyncMock() | ||
| mock_stream.recv = mock.AsyncMock() | ||
| data = b"a" * _DEFAULT_FLUSH_INTERVAL_BYTES | ||
| await writer.append(data) | ||
| writer.simple_flush.assert_awaited_once() | ||
| num_chunks = _DEFAULT_FLUSH_INTERVAL_BYTES // _MAX_CHUNK_SIZE_BYTES | ||
| assert mock_stream.send.await_count == num_chunks | ||
| # All but the last request should not have flush or state_lookup set. | ||
| for i in range(num_chunks - 1): | ||
| request = mock_stream.send.await_args_list[i].args[0] | ||
| assert not request.flush | ||
| assert not request.state_lookup | ||
| # The last request should have flush and state_lookup set. | ||
| last_request = mock_stream.send.await_args_list[-1].args[0] | ||
| assert last_request.flush | ||
| assert last_request.state_lookup | ||
| assert writer.bytes_appended_since_last_flush == 0 | ||
| @pytest.mark.asyncio | ||
| @@ -561,12 +588,18 @@ async def test_append_handles_large_data(mock_write_object_stream, mock_client): | ||
| writer.persisted_size = 0 | ||
| mock_stream = mock_write_object_stream.return_value | ||
| mock_stream.send = mock.AsyncMock() | ||
| writer.simple_flush = mock.AsyncMock() | ||
| mock_stream.recv = mock.AsyncMock() | ||
| data = b"a" * (_DEFAULT_FLUSH_INTERVAL_BYTES * 2 + 1) | ||
| await writer.append(data) | ||
| assert writer.simple_flush.await_count == 2 | ||
| flushed_requests = [ | ||
| call.args[0] for call in mock_stream.send.await_args_list if call.args[0].flush | ||
| ] | ||
| assert len(flushed_requests) == 3 | ||
| last_request = mock_stream.send.await_args_list[-1].args[0] | ||
| assert last_request.state_lookup | ||
| @pytest.mark.asyncio | ||
| @@ -584,17 +617,35 @@ async def test_append_data_two_times(mock_write_object_stream, mock_client): | ||
| writer.persisted_size = 0 | ||
| mock_stream = mock_write_object_stream.return_value | ||
| mock_stream.send = mock.AsyncMock() | ||
| writer.simple_flush = mock.AsyncMock() | ||
| data1 = b"a" * (_MAX_CHUNK_SIZE_BYTES + 10) | ||
| mock_stream.recv = mock.AsyncMock( | ||
| return_value=_storage_v2.BidiWriteObjectResponse( | ||
| persisted_size= len(data1) | ||
| ) | ||
| ) | ||
| await writer.append(data1) | ||
| assert mock_stream.send.await_count == 2 | ||
| last_request_data1 = mock_stream.send.await_args_list[-1].args[0] | ||
| assert last_request_data1.flush | ||
| assert last_request_data1.state_lookup | ||
| data2 = b"b" * (_MAX_CHUNK_SIZE_BYTES + 20) | ||
| mock_stream.recv = mock.AsyncMock( | ||
| return_value=_storage_v2.BidiWriteObjectResponse( | ||
| persisted_size= len(data2) + len(data1) | ||
| ) | ||
| ) | ||
| await writer.append(data2) | ||
| assert mock_stream.send.await_count == 4 | ||
| last_request_data2 = mock_stream.send.await_args_list[-1].args[0] | ||
| assert last_request_data2.flush | ||
| assert last_request_data2.state_lookup | ||
| total_data_length = len(data1) + len(data2) | ||
| assert writer.offset == total_data_length | ||
| assert writer.simple_flush.await_count == 0 | ||
| @pytest.mark.asyncio | ||
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.