This is an automated email from the ASF dual-hosted git repository.

samt pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git

commit 62bd9ab1ae206a3582eebbff9dbfa8855f479333
Author: Sam Tunnicliffe <[email protected]>
AuthorDate: Fri Nov 7 17:29:21 2025 +0000

    Gossip entries for hibernating non-members don't block truncate
    
    Patch by Sam Tunnicliffe; reviewed by Marcus Eriksson for CASSANDRA-21003
---
 CHANGES.txt                                        |  1 +
 src/java/org/apache/cassandra/gms/Gossiper.java    |  5 ++
 .../test/HibernatingGossipOnlyMembersTest.java     | 67 ++++++++++++++++++++++
 3 files changed, 73 insertions(+)

diff --git a/CHANGES.txt b/CHANGES.txt
index bf947bc7e2..0f7cf787f4 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 5.1
+ * Gossip entries for hibernating non-members don't block truncate 
(CASSANDRA-21003)
  * Retry without time limit calculates wait time incorrectly (CASSANDRA-21002)
  * Don't submit AlterSchemaStatements which produce no effect locally to the 
CMS (CASSANDRA-21001)
  * Avoid iterating all prepared statements when getting 
PreparedStatementsCacheSize metric (CASSANDRA-21038)
diff --git a/src/java/org/apache/cassandra/gms/Gossiper.java 
b/src/java/org/apache/cassandra/gms/Gossiper.java
index d6aebf1ba8..386bb0e13a 100644
--- a/src/java/org/apache/cassandra/gms/Gossiper.java
+++ b/src/java/org/apache/cassandra/gms/Gossiper.java
@@ -402,6 +402,11 @@ public class Gossiper implements 
IFailureDetectionEventListener, GossiperMBean,
         for (InetAddressAndPort endpoint : unreachableEndpoints.keySet())
         {
             NodeId nodeId = metadata.directory.peerId(endpoint);
+            // If the endpoint is not known to cluster metadata, the peer is 
probably a leftover of upgrading as
+            // long-gone hibernating peers may persist indefinitely. By 
definition though, such a peer cannot be
+            // a token owner, so it is safe to exclude it.
+            if (nodeId == null)
+                continue;
             NodeState state = metadata.directory.peerState(nodeId);
             switch (state)
             {
diff --git 
a/test/distributed/org/apache/cassandra/distributed/test/HibernatingGossipOnlyMembersTest.java
 
b/test/distributed/org/apache/cassandra/distributed/test/HibernatingGossipOnlyMembersTest.java
new file mode 100644
index 0000000000..4a0c1dd039
--- /dev/null
+++ 
b/test/distributed/org/apache/cassandra/distributed/test/HibernatingGossipOnlyMembersTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.distributed.test;
+
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+import org.apache.cassandra.distributed.Cluster;
+import org.apache.cassandra.distributed.api.ConsistencyLevel;
+import org.apache.cassandra.distributed.api.IIsolatedExecutor;
+import org.apache.cassandra.gms.ApplicationState;
+import org.apache.cassandra.gms.EndpointState;
+import org.apache.cassandra.gms.Gossiper;
+import org.apache.cassandra.gms.HeartBeatState;
+import org.apache.cassandra.gms.VersionedValue;
+import org.apache.cassandra.locator.InetAddressAndPort;
+
+public class HibernatingGossipOnlyMembersTest extends TestBaseImpl
+{
+    @Test
+    public void testTruncationWithHibernatingGossipOnlyNodes() throws Exception
+    {
+        try (Cluster cluster = init(Cluster.build(2)
+                                           .start()))
+        {
+            cluster.schemaChange("create table " + KEYSPACE + ".tbl (id int 
primary key, t int)");
+            cluster.get(1).runOnInstance(injectPeerIntoGossip("127.0.0.99"));
+            // Even though the hibernating peer is considered unreachable, it 
won't prevent the truncation
+            cluster.coordinator(1).execute("TRUNCATE " + KEYSPACE + ".tbl", 
ConsistencyLevel.ALL);
+        }
+    }
+
+    /**
+     * Add a hibernating peer to gossip which is not a member of the cluster. 
This can be a legacy
+     * of upgrades, where previously this kind of non-member were never purged 
from gossip.
+     */
+    private IIsolatedExecutor.SerializableRunnable injectPeerIntoGossip(String 
address)
+    {
+        return () -> {
+            Map<ApplicationState, VersionedValue> appState = new 
EnumMap<>(ApplicationState.class);
+            appState.put(ApplicationState.STATUS_WITH_PORT, 
VersionedValue.unsafeMakeVersionedValue("hibernate,true", 1));
+            EndpointState epState = new EndpointState(HeartBeatState.empty(), 
appState);
+            Map<InetAddressAndPort, EndpointState> hibernating = new 
HashMap<>();
+            hibernating.put(InetAddressAndPort.getByNameUnchecked(address), 
epState);
+            Gossiper.runInGossipStageBlocking(() -> 
Gossiper.instance.applyStateLocally(hibernating));
+        };
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to