Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/next-release/bugfix-crt-24627.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "bugfix",
"category": "crt",
"description": "Return error when final rename task fails on downloads"
}
7 changes: 5 additions & 2 deletions awscli/s3transfer/crt.py
Original file line number Diff line number Diff line change
Expand Up @@ -1699,8 +1699,11 @@ def __call__(self, **kwargs):
)
except Exception as e:
self._osutil.remove_file(self._temp_filename)
# the CRT future has done already at this point
self._coordinator.set_exception(e)
# This runs as an on_done callback, so the transfer is already
# marked complete and the exception has to override that
# result. Otherwise the download reports success having
# written nothing.
self._coordinator.set_exception(e, override=True)


class AfterDoneHandler:
Expand Down
41 changes: 40 additions & 1 deletion tests/unit/s3transfer/test_crt.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from botocore.session import Session
from s3transfer.constants import GB
from s3transfer.exceptions import TransferNotDoneError
from s3transfer.utils import CallArgs
from s3transfer.utils import CallArgs, OSUtils

from tests import HAS_CRT, FileCreator, mock, requires_crt, unittest

Expand Down Expand Up @@ -876,3 +876,42 @@ def test_fio_options(
mock_s3_crt_client.call_args[1]['fio_options'].direct_io
is direct_io
)


@requires_crt_pytest
class TestRenameTempFileHandler:
@pytest.fixture
def coordinator(self):
return s3transfer.crt.CRTTransferCoordinator()

@pytest.fixture
def osutil(self):
return mock.Mock(spec=OSUtils)

@pytest.fixture
def handler(self, coordinator, osutil):
return s3transfer.crt.RenameTempFileHandler(
coordinator, 'final', 'temp', osutil
)

def test_renames_temp_file(self, handler, osutil):
handler(error=None)
osutil.rename_file.assert_called_once_with('temp', 'final')

def test_removes_temp_file_on_transfer_error(self, handler, osutil):
handler(error=Exception('transfer failed'))
osutil.remove_file.assert_called_once_with('temp')
assert not osutil.rename_file.called

def test_surfaces_rename_error(self, coordinator, handler, osutil):
osutil.rename_file.side_effect = OSError('Is a directory')
# The handler runs as an on done callback, so the transfer is already
# complete by the time the rename fails.
coordinator.complete()
assert coordinator.done()

handler(error=None)

osutil.remove_file.assert_called_once_with('temp')
with pytest.raises(OSError):
coordinator.result()
Loading