Skip to content

Commit 3b69e2a

Browse files
committed
policies: prevent retry downgrade from serial to non-serial consistency
Add a guard in the retry execution path that prevents any retry policy from downgrading SERIAL/LOCAL_SERIAL to a non-serial consistency level, which would break serial read (Paxos) guarantees. Also add a unit test verifying DowngradingConsistencyRetryPolicy does not downgrade serial consistency on read timeout or unavailable.
1 parent c14edfa commit 3b69e2a

2 files changed

Lines changed: 43 additions & 1 deletion

File tree

cassandra/cluster.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5433,7 +5433,20 @@ def _retry(self, reuse_connection, consistency_level, host, delay):
54335433
if self._metrics is not None:
54345434
self._metrics.on_retry()
54355435
if consistency_level is not None:
5436-
self.message.consistency_level = consistency_level
5436+
# Never downgrade from serial to non-serial consistency, as that
5437+
# would break serial read (Paxos) guarantees.
5438+
if not ConsistencyLevel.is_serial(consistency_level):
5439+
original_cl = self.message.consistency_level
5440+
if ConsistencyLevel.is_serial(original_cl):
5441+
log.debug(
5442+
"Retry policy attempted to downgrade serial consistency %s to %s; "
5443+
"keeping original consistency level.",
5444+
ConsistencyLevel.value_to_name.get(original_cl, original_cl),
5445+
ConsistencyLevel.value_to_name.get(consistency_level, consistency_level))
5446+
else:
5447+
self.message.consistency_level = consistency_level
5448+
else:
5449+
self.message.consistency_level = consistency_level
54375450

54385451
# don't retry on the event loop thread
54395452
self.session.cluster.scheduler.schedule(delay, self._retry_task, reuse_connection, host)

tests/unit/test_policies.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,35 @@ def test_unavailable(self):
14181418
assert retry == RetryPolicy.RETRY
14191419
assert consistency == ConsistencyLevel.ONE
14201420

1421+
def test_serial_consistency_not_downgraded(self):
1422+
"""
1423+
Test that SERIAL/LOCAL_SERIAL consistency is never downgraded
1424+
to a non-serial consistency level by the retry policy.
1425+
@jira_ticket PYTHON-1394
1426+
@expected_result retry policy should rethrow or retry on next host
1427+
without downgrading serial consistency
1428+
1429+
@test_category policy
1430+
"""
1431+
policy = DowngradingConsistencyRetryPolicy()
1432+
1433+
for cl in (ConsistencyLevel.SERIAL, ConsistencyLevel.LOCAL_SERIAL):
1434+
# on_read_timeout should rethrow for serial consistency
1435+
retry, consistency = policy.on_read_timeout(
1436+
query=None, consistency=cl, required_responses=3,
1437+
received_responses=1, data_retrieved=True, retry_num=0)
1438+
assert retry == RetryPolicy.RETHROW, \
1439+
"Expected RETHROW for serial consistency %s on read timeout" % cl
1440+
assert consistency is None
1441+
1442+
# on_unavailable should retry on next host without downgrading
1443+
retry, consistency = policy.on_unavailable(
1444+
query=None, consistency=cl, required_replicas=3,
1445+
alive_replicas=1, retry_num=0)
1446+
assert retry == RetryPolicy.RETRY_NEXT_HOST, \
1447+
"Expected RETRY_NEXT_HOST for serial consistency %s on unavailable" % cl
1448+
assert consistency is None
1449+
14211450

14221451
class ExponentialRetryPolicyTest(unittest.TestCase):
14231452
def test_calculate_backoff(self):

0 commit comments

Comments
 (0)