Skip to content

Commit ce324f0

Browse files
committed
cluster/pool: handle stale up and replacement races
1 parent 81d56f2 commit ce324f0

4 files changed

Lines changed: 69 additions & 2 deletions

File tree

cassandra/cluster.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2411,8 +2411,9 @@ def _clear_stale_reconnector():
24112411
host, is_host_addition=False, allow_retry_after_auth_failure=True)
24122412
if future is not None:
24132413
have_future = True
2414-
future.add_done_callback(callback)
24152414
futures.add(future)
2415+
for future in tuple(futures):
2416+
future.add_done_callback(callback)
24162417
except Exception:
24172418
log.exception("Unexpected failure handling node %s being marked up:", host)
24182419
for future in futures:

cassandra/pool.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,13 @@ def _replace(self, connection):
705705
return
706706

707707
if keyspace:
708-
replacement_connection.set_keyspace_blocking(keyspace)
708+
try:
709+
replacement_connection.set_keyspace_blocking(keyspace)
710+
except Exception:
711+
log.warning("Failed reconnecting %s. Retrying." % (expected_endpoint,))
712+
replacement_connection.close()
713+
self._session.submit(self._replace, connection)
714+
return
709715

710716
with self._lock:
711717
if self.is_shutdown:

tests/unit/test_cluster.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,34 @@ def test_noop_down_during_up_handling_does_not_supersede_up(self):
10901090
assert host.is_up
10911091
assert state.up_epoch is None
10921092

1093+
def test_on_up_waits_for_all_pool_futures_when_one_is_already_done(self):
1094+
completed_pool_future = Future()
1095+
completed_pool_future.set_result(True)
1096+
pending_pool_future = Future()
1097+
completed_session = Mock()
1098+
completed_session.add_or_renew_pool.return_value = completed_pool_future
1099+
pending_session = Mock()
1100+
pending_session.add_or_renew_pool.return_value = pending_pool_future
1101+
listener = Mock()
1102+
cluster = self._make_cluster(listener=listener)
1103+
cluster.sessions = [completed_session, pending_session]
1104+
cluster._prepare_all_queries = Mock()
1105+
cluster.profile_manager.distance.return_value = HostDistance.LOCAL
1106+
host = self._make_host()
1107+
host.set_down()
1108+
1109+
Cluster.on_up(cluster, host)
1110+
1111+
assert not host.is_up
1112+
listener.on_up.assert_not_called()
1113+
1114+
pending_pool_future.set_result(False)
1115+
1116+
assert not host.is_up
1117+
listener.on_up.assert_not_called()
1118+
cluster._start_reconnector.assert_called_once_with(
1119+
host, is_host_addition=False, expected_endpoint=host.endpoint)
1120+
10931121
def test_newer_forced_down_during_up_handling_is_preserved(self):
10941122
pool_future = Future()
10951123
session = Mock()

tests/unit/test_host_connection_pool.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,3 +350,35 @@ def mock_connection_factory(self, *args, **kwargs):
350350

351351
# Cleanup executor with proper wait
352352
session.cluster.executor.shutdown(wait=True)
353+
354+
def test_replace_retries_when_replacement_keyspace_set_fails(self):
355+
host = Host(DefaultEndPoint('127.0.0.1'), SimpleConvictionPolicy,
356+
host_id=uuid.uuid4())
357+
session = NonCallableMagicMock(spec=Session, keyspace='ks')
358+
session.cluster = MagicMock()
359+
session.cluster.shard_aware_options = ShardAwareOptions()
360+
session.cluster._endpoints_match.side_effect = Cluster._endpoints_match
361+
initial_connection = HashableMock(
362+
spec=Connection, in_flight=0, is_defunct=False, is_closed=False,
363+
max_request_id=100, signaled_error=False,
364+
orphaned_threshold_reached=False,
365+
features=ProtocolFeatures(shard_id=0))
366+
replacement_connection = HashableMock(
367+
spec=Connection, in_flight=0, is_defunct=False, is_closed=False,
368+
max_request_id=100, signaled_error=False,
369+
orphaned_threshold_reached=False,
370+
features=ProtocolFeatures(shard_id=0))
371+
replacement_connection.set_keyspace_blocking.side_effect = RuntimeError(
372+
"keyspace failed")
373+
session.cluster.connection_factory.side_effect = [
374+
initial_connection, replacement_connection]
375+
376+
pool = HostConnection(host, HostDistance.LOCAL, session)
377+
pool._is_replacing = True
378+
379+
pool._replace(initial_connection)
380+
381+
assert session.submit.call_count == 1
382+
submitted_fn, submitted_connection = session.submit.call_args.args
383+
assert submitted_fn == pool._replace
384+
assert submitted_connection is initial_connection

0 commit comments

Comments
 (0)