Skip to content

Commit f68e22d

Browse files
gillesbiannictipabu
authored andcommitted
Stop retrying every deletes in container-sync
Internal clients used to retry operations even on client errors, where we had no expectation that we would get a different response. Among other things, this would cause container-sync to needlessly retry deletes that would 404, even though we had code specifically in place to say that 404s are OK. While we're at it, also flag 409s as being OK (since the remote has newer data than us). Change-Id: I1833a8ff2ac2f4126b6274d6bba47e2b58a41745 Closes-Bug: #1849841
1 parent 73a0e8e commit f68e22d

File tree

4 files changed

+60
-8
lines changed

4 files changed

+60
-8
lines changed

swift/common/internal_client.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from swift.common.constraints import AUTO_CREATE_ACCOUNT_PREFIX
2929
from swift.common.exceptions import ClientException
3030
from swift.common.http import (HTTP_NOT_FOUND, HTTP_MULTIPLE_CHOICES,
31-
is_server_error)
31+
is_client_error, is_server_error)
3232
from swift.common.swob import Request, bytes_to_wsgi
3333
from swift.common.utils import quote, closing_if_possible
3434
from swift.common.wsgi import loadapp, pipeline_property
@@ -930,14 +930,17 @@ def retry_request(self, method, **kwargs):
930930
self.attempts += 1
931931
try:
932932
return self.base_request(method, **kwargs)
933+
except urllib2.HTTPError as err:
934+
if is_client_error(err.getcode() or 500):
935+
raise ClientException('Client error',
936+
http_status=err.getcode())
937+
elif self.attempts > retries:
938+
raise ClientException('Raise too many retries',
939+
http_status=err.getcode())
933940
except (socket.error, httplib.HTTPException, urllib2.URLError) \
934941
as err:
935942
if self.attempts > retries:
936-
if isinstance(err, urllib2.HTTPError):
937-
raise ClientException('Raise too many retries',
938-
http_status=err.getcode())
939-
else:
940-
raise
943+
raise
941944
sleep(backoff)
942945
backoff = min(backoff * 2, self.max_backoff)
943946

swift/container/sync.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
FileLikeIter, get_logger, hash_path, quote, validate_sync_to,
4343
whataremyips, Timestamp, decode_timestamps)
4444
from swift.common.daemon import Daemon
45-
from swift.common.http import HTTP_UNAUTHORIZED, HTTP_NOT_FOUND
45+
from swift.common.http import HTTP_UNAUTHORIZED, HTTP_NOT_FOUND, HTTP_CONFLICT
4646
from swift.common.wsgi import ConfigString
4747

4848

@@ -561,7 +561,8 @@ def container_sync_row(self, row, sync_to, user_key, broker, info,
561561
logger=self.logger,
562562
timeout=self.conn_timeout)
563563
except ClientException as err:
564-
if err.http_status != HTTP_NOT_FOUND:
564+
if err.http_status not in (
565+
HTTP_NOT_FOUND, HTTP_CONFLICT):
565566
raise
566567
self.container_deletes += 1
567568
self.container_stats['deletes'] += 1

test/unit/common/test_internal_client.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,6 +1600,36 @@ def test_request_object_with_retries_with_HTTPError(self,
16001600
self.assertEqual(mock_sleep.call_count, 1)
16011601
self.assertEqual(mock_urlopen.call_count, 2)
16021602

1603+
@mock.patch.object(urllib2, 'urlopen')
1604+
def test_delete_object_with_404_no_retry(self, mock_urlopen):
1605+
mock_response = mock.MagicMock()
1606+
mock_response.read.return_value = b''
1607+
err_args = [None, 404, None, None, None]
1608+
mock_urlopen.side_effect = urllib2.HTTPError(*err_args)
1609+
1610+
with mock.patch('swift.common.internal_client.sleep') as mock_sleep, \
1611+
self.assertRaises(exceptions.ClientException) as caught:
1612+
internal_client.delete_object('http://127.0.0.1',
1613+
container='con', name='obj')
1614+
self.assertEqual(caught.exception.http_status, 404)
1615+
self.assertEqual(mock_sleep.call_count, 0)
1616+
self.assertEqual(mock_urlopen.call_count, 1)
1617+
1618+
@mock.patch.object(urllib2, 'urlopen')
1619+
def test_delete_object_with_409_no_retry(self, mock_urlopen):
1620+
mock_response = mock.MagicMock()
1621+
mock_response.read.return_value = b''
1622+
err_args = [None, 409, None, None, None]
1623+
mock_urlopen.side_effect = urllib2.HTTPError(*err_args)
1624+
1625+
with mock.patch('swift.common.internal_client.sleep') as mock_sleep, \
1626+
self.assertRaises(exceptions.ClientException) as caught:
1627+
internal_client.delete_object('http://127.0.0.1',
1628+
container='con', name='obj')
1629+
self.assertEqual(caught.exception.http_status, 409)
1630+
self.assertEqual(mock_sleep.call_count, 0)
1631+
self.assertEqual(mock_urlopen.call_count, 1)
1632+
16031633
def test_proxy(self):
16041634
# check that proxy arg is passed through to the urllib Request
16051635
scheme = 'http'

test/unit/container/test_sync.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,24 @@ def fake_delete_object(*args, **kwargs):
909909
self.assertEqual(cs.container_deletes, 2)
910910
self.assertEqual(len(exc), 3)
911911
self.assertEqual(str(exc[-1]), 'test client exception: 404')
912+
913+
def fake_delete_object(*args, **kwargs):
914+
exc.append(ClientException('test client exception',
915+
http_status=409))
916+
raise exc[-1]
917+
918+
sync.delete_object = fake_delete_object
919+
# Success because our tombstone is out of date
920+
self.assertTrue(cs.container_sync_row(
921+
{'deleted': True,
922+
'name': 'object',
923+
'created_at': '1.2'}, 'http://sync/to/path',
924+
'key', FakeContainerBroker('broker'),
925+
{'account': 'a', 'container': 'c', 'storage_policy_index': 0},
926+
realm, realm_key))
927+
self.assertEqual(cs.container_deletes, 3)
928+
self.assertEqual(len(exc), 4)
929+
self.assertEqual(str(exc[-1]), 'test client exception: 409')
912930
finally:
913931
sync.uuid = orig_uuid
914932
sync.delete_object = orig_delete_object

0 commit comments

Comments
 (0)