-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix(storage): respect deadline in transfer_manager.upload_many #17960
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,9 +12,12 @@ | |
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| import concurrent.futures | ||
| import io | ||
| import os | ||
| import pickle | ||
| import tempfile | ||
| import time | ||
|
|
||
| import mock | ||
| import pytest | ||
|
|
@@ -114,6 +117,7 @@ def test_upload_many_passes_concurrency_options(): | |
| mock.patch("concurrent.futures.ThreadPoolExecutor") as pool_patch, | ||
| mock.patch("concurrent.futures.wait") as wait_patch, | ||
| ): | ||
| wait_patch.return_value = ({pool_patch.return_value.submit.return_value}, set()) | ||
| transfer_manager.upload_many( | ||
| FILE_BLOB_PAIRS, | ||
| deadline=DEADLINE, | ||
|
|
@@ -135,6 +139,7 @@ def test_threads_deprecation_with_upload(): | |
| mock.patch("concurrent.futures.ThreadPoolExecutor") as pool_patch, | ||
| mock.patch("concurrent.futures.wait") as wait_patch, | ||
| ): | ||
| wait_patch.return_value = ({pool_patch.return_value.submit.return_value}, set()) | ||
| with pytest.warns(): | ||
| transfer_manager.upload_many( | ||
| FILE_BLOB_PAIRS, deadline=DEADLINE, threads=MAX_WORKERS | ||
|
|
@@ -189,6 +194,57 @@ def test_upload_many_raises_exceptions(): | |
| ) | ||
|
|
||
|
|
||
| def test_upload_many_raises_timeout_error_when_deadline_exceeded(): | ||
| # Thread-mode: A stuck upload must not make upload_many hang past the deadline. | ||
| def blocking_upload(*args, **kwargs): | ||
| time.sleep(5) | ||
|
|
||
| mock_blob = mock.Mock(spec=Blob) | ||
| mock_blob._prep_and_do_upload.side_effect = blocking_upload | ||
|
|
||
| with pytest.raises(concurrent.futures.TimeoutError): | ||
| transfer_manager.upload_many( | ||
| [(io.BytesIO(b"data"), mock_blob)], | ||
| worker_type=transfer_manager.THREAD, | ||
| deadline=0.1, | ||
| ) | ||
|
Comment on lines
+197
to
+210
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of spawning a real background thread that sleeps for 5 seconds (which can slow down the test suite and potentially cause flakiness), we can mock def test_upload_many_raises_timeout_error_when_deadline_exceeded():
# Thread-mode: A stuck upload must not make upload_many hang past the deadline.
with mock.patch("concurrent.futures.wait") as wait_patch:
# A non-empty not_done set signals the deadline was exceeded.
wait_patch.return_value = (set(), {concurrent.futures.Future()})
with pytest.raises(concurrent.futures.TimeoutError):
transfer_manager.upload_many(
[(io.BytesIO(b"data"), mock.Mock(spec=Blob))],
worker_type=transfer_manager.THREAD,
deadline=0.1,
)
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My opinion:
I will let the reviewer decide. |
||
|
|
||
|
|
||
| def test_upload_many_terminates_process_workers_on_deadline(): | ||
| # Process-mode: A stuck upload must not make upload_many hang past the deadline. | ||
| # After the deadline, the worker processes should be terminated. | ||
| fake_processes = {1: mock.Mock(), 2: mock.Mock()} | ||
|
|
||
| class FakeProcessPoolExecutor(concurrent.futures.ProcessPoolExecutor): | ||
| def __init__(self, *args, **kwargs): | ||
| self._processes = fake_processes | ||
|
|
||
| def submit(self, *args, **kwargs): | ||
| return concurrent.futures.Future() | ||
|
|
||
| def shutdown(self, *args, **kwargs): | ||
| pass | ||
|
|
||
| with ( | ||
| mock.patch( | ||
| "google.cloud.storage.transfer_manager._get_pool_class_and_requirements", | ||
| return_value=(FakeProcessPoolExecutor, False), | ||
| ), | ||
| mock.patch("concurrent.futures.wait") as wait_patch, | ||
| ): | ||
| # A non-empty not_done set signals the deadline was exceeded. | ||
| wait_patch.return_value = (set(), {concurrent.futures.Future()}) | ||
| with pytest.raises(concurrent.futures.TimeoutError): | ||
| transfer_manager.upload_many( | ||
| [("file_a.txt", mock.Mock(spec=Blob))], | ||
| worker_type=transfer_manager.PROCESS, | ||
| deadline=0.1, | ||
| ) | ||
|
|
||
| for process in fake_processes.values(): | ||
| process.terminate.assert_called_once_with() | ||
|
rameshvarun marked this conversation as resolved.
|
||
|
|
||
|
|
||
| def test_upload_many_suppresses_412_with_skip_if_exists(): | ||
| FILE_BLOB_PAIRS = [ | ||
| ("file_a.txt", mock.Mock(spec=Blob)), | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.