2929import org .slf4j .Logger ;
3030import org .slf4j .LoggerFactory ;
3131
32+ import org .apache .cassandra .concurrent .ScheduledExecutors ;
33+ import org .apache .cassandra .config .DatabaseDescriptor ;
3234import org .apache .cassandra .db .ColumnFamilyStore ;
3335import org .apache .cassandra .db .SystemKeyspace ;
3436import org .apache .cassandra .db .virtual .PeersTable ;
5456import static org .apache .cassandra .tcm .membership .NodeState .MOVING ;
5557import static org .apache .cassandra .tcm .membership .NodeState .REGISTERED ;
5658
57- public class LegacyStateListener implements ChangeListener . Async
59+ public class LegacyStateListener implements ChangeListener
5860{
5961 private static final Logger logger = LoggerFactory .getLogger (LegacyStateListener .class );
6062
@@ -75,52 +77,97 @@ public void notifyPostCommit(ClusterMetadata prev, ClusterMetadata next, boolean
7577 changed .add (node );
7678 }
7779
78- for (InetAddressAndPort remove : removedAddr )
80+ // next.myNodeId() can be null during replay (before we have registered) but if it is present and
81+ // there is a relevant change to the state of the local node, process that synchronously.
82+ if (next .myNodeId () != null && changed .contains (next .myNodeId ()))
7983 {
80- GossipHelper .removeFromGossip (remove );
81- GossipHelper .evictFromMembership (remove );
82- PeersTable .removeFromSystemPeersTables (remove );
84+ // Default is to process updates for the local node synchronously, overridable via config/hotprop
85+ if (DatabaseDescriptor .getLegacyStateListenerSyncLocalUpdates ())
86+ processChangesToLocalState (prev , next , next .myNodeId ());
87+ else
88+ ScheduledExecutors .optionalTasks .submit (() -> processChangesToLocalState (prev , next , next .myNodeId ()));
89+
90+ changed .remove (next .myNodeId ());
8391 }
8492
85- for (NodeId change : changed )
93+ // Schedule async processing of changes to peers and removing unregistered nodes (potentially including the
94+ // local node).
95+ ScheduledExecutors .optionalTasks .submit (() -> {
96+ processRemovedNodes (removedAddr );
97+ processChangesToRemotePeers (prev , next , changed );
98+ });
99+ }
100+
101+ private void processChangesToLocalState (ClusterMetadata prev , ClusterMetadata next , NodeId localId )
102+ {
103+ logger .info ("Processing changes to local node state {} for epoch {}->{}" , localId , prev .epoch .getEpoch (), next .epoch .getEpoch ());
104+ Collection <Token > tokensForGossip = next .tokenMap .tokens (localId );
105+ NodeState state = next .directory .peerState (localId );
106+ switch (state )
86107 {
87- // next.myNodeId() can be null during replay (before we have registered)
88- if (next .myNodeId () != null && next .myNodeId ().equals (change ))
89- {
90- switch (next .directory .peerState (change ))
108+ case BOOTSTRAPPING :
109+ case BOOT_REPLACING :
110+ // For compatibility with clients, ensure we set TOKENS for bootstrapping nodes in gossip.
111+ // As these are not yet added to the token map they must be extracted from the in progress sequence.
112+ tokensForGossip = GossipHelper .getTokensFromOperation (localId , next );
113+ if (state == BOOTSTRAPPING && prev .directory .peerState (localId ) != BOOTSTRAPPING )
91114 {
92- case BOOTSTRAPPING :
93- if (prev .directory .peerState (change ) != BOOTSTRAPPING )
94- {
95- // legacy log messages for tests
96- logger .info ("JOINING: Starting to bootstrap" );
97- logger .info ("JOINING: calculation complete, ready to bootstrap" );
98- }
99- break ;
100- case BOOT_REPLACING :
101- case REGISTERED :
102- break ;
103- case JOINED :
104- SystemKeyspace .updateTokens (next .directory .endpoint (change ), next .tokenMap .tokens (change ));
105- // needed if we miss the REGISTERED above; Does nothing if we are already in epStateMap:
106- Gossiper .instance .maybeInitializeLocalState (SystemKeyspace .incrementAndGetGeneration ());
107- StreamSupport .stream (ColumnFamilyStore .all ().spliterator (), false )
108- .filter (cfs -> Schema .instance .getUserKeyspaces ().names ().contains (cfs .keyspace .getName ()))
109- .forEach (cfs -> cfs .indexManager .executePreJoinTasksBlocking (true ));
110- if (prev .directory .peerState (change ) == MOVING )
111- logger .info ("Node {} state jump to NORMAL" , next .directory .endpoint (change ));
112- break ;
115+ // legacy log messages for tests
116+ logger .info ("JOINING: Starting to bootstrap" );
117+ logger .info ("JOINING: calculation complete, ready to bootstrap" );
113118 }
114- // Maybe intitialise local epstate whatever the node state because we could be processing after a
115- // replay and so may have not seen any previous local states, making this the first mutation of gossip
116- // state for the local node.
117- Gossiper .instance .maybeInitializeLocalState (SystemKeyspace .incrementAndGetGeneration ());
118- Gossiper .instance .addLocalApplicationState (SCHEMA , StorageService .instance .valueFactory .schema (next .schema .getVersion ()));
119- // if the local node's location has changed, update system.local.
120- if (!next .directory .location (change ).equals (prev .directory .location (change )))
121- SystemKeyspace .updateLocation (next .directory .location (change ));
122- }
119+ break ;
120+ case JOINED :
121+ tokensForGossip = next .tokenMap .tokens (localId );
122+ SystemKeyspace .updateTokens (next .directory .endpoint (localId ), tokensForGossip );
123+ Set <String > userKeyspaces = Schema .instance .getUserKeyspaces ().names ();
124+ StreamSupport .stream (ColumnFamilyStore .all ().spliterator (), false )
125+ .filter (cfs -> userKeyspaces .contains (cfs .keyspace .getName ()))
126+ .forEach (cfs -> cfs .indexManager .executePreJoinTasksBlocking (true ));
127+ NodeState previousState = prev .directory .peerState (localId );
128+ if (previousState == MOVING )
129+ {
130+ logger .info ("Node {} state jump to NORMAL" , next .directory .endpoint (localId ));
131+ }
132+ else if (previousState == BOOT_REPLACING )
133+ {
134+ // legacy log message for compatibility (& tests)
135+ MultiStepOperation <?> sequence = prev .inProgressSequences .get (localId );
136+ if (sequence != null && sequence .kind () == MultiStepOperation .Kind .REPLACE )
137+ {
138+ logCompletedReplacement (prev .directory , (BootstrapAndReplace ) sequence );
139+ tokensForGossip = GossipHelper .getTokensFromOperation (sequence );
140+ }
141+ }
142+ break ;
143+ case MOVING :
144+ logger .debug ("Node {} state MOVING, tokens {}" , next .directory .endpoint (localId ), prev .tokenMap .tokens (localId ));
145+ tokensForGossip = next .tokenMap .tokens (localId );
146+ break ;
147+ case LEFT :
148+ tokensForGossip = prev .tokenMap .tokens (localId );
149+ break ;
150+ }
123151
152+ // Maybe initialise local epstate whatever the node state because we could be processing after a
153+ // replay and so may have not seen any previous local states, making this the first mutation of gossip
154+ // state for the local node.
155+ Gossiper .instance .maybeInitializeLocalState (SystemKeyspace .incrementAndGetGeneration ());
156+ Gossiper .instance .addLocalApplicationState (SCHEMA , StorageService .instance .valueFactory .schema (next .schema .getVersion ()));
157+ // Pull node properties from cluster metadata into gossip, except if the node is only in the REGISTERED state
158+ // as that has no equivalent gossip STATUS
159+ if (state != REGISTERED )
160+ Gossiper .instance .mergeNodeToGossip (localId , next , tokensForGossip );
161+ // if the local node's location has changed, update system.local.
162+ if (!next .directory .location (localId ).equals (prev .directory .location (localId )))
163+ SystemKeyspace .updateLocation (next .directory .location (localId ));
164+ }
165+
166+ private void processChangesToRemotePeers (ClusterMetadata prev , ClusterMetadata next , Set <NodeId > changed )
167+ {
168+ for (NodeId change : changed )
169+ {
170+ logger .info ("Processing changes to peer {} for epoch {}->{}" , change , prev .epoch .getEpoch (), next .epoch .getEpoch ());
124171 if (next .directory .peerState (change ) == REGISTERED )
125172 {
126173 // Re-establish any connections made prior to this node registering
@@ -155,21 +202,11 @@ else if (NodeState.isBootstrap(next.directory.peerState(change)))
155202 }
156203 else if (prev .directory .peerState (change ) == BOOT_REPLACING )
157204 {
158- // legacy log message for compatibility (& tests)
159205 MultiStepOperation <?> sequence = prev .inProgressSequences .get (change );
160206 if (sequence != null && sequence .kind () == MultiStepOperation .Kind .REPLACE )
161207 {
162- BootstrapAndReplace replace = (BootstrapAndReplace ) sequence ;
163- InetAddressAndPort replaced = prev .directory .endpoint (replace .startReplace .replaced ());
164- InetAddressAndPort replacement = prev .directory .endpoint (change );
165- Collection <Token > tokens = GossipHelper .getTokensFromOperation (replace );
166- logger .info ("Node {} will complete replacement of {} for tokens {}" , replacement , replaced , tokens );
167- if (!replacement .equals (replaced ))
168- {
169- for (Token token : tokens )
170- logger .warn ("Token {} changing ownership from {} to {}" , token , replaced , replacement );
171- }
172- Gossiper .instance .mergeNodeToGossip (change , next , tokens );
208+ logCompletedReplacement (prev .directory , (BootstrapAndReplace ) sequence );
209+ Gossiper .instance .mergeNodeToGossip (change , next , GossipHelper .getTokensFromOperation (sequence ));
173210 PeersTable .updateLegacyPeerTable (change , prev , next );
174211 }
175212 }
@@ -181,6 +218,29 @@ else if (prev.directory.peerState(change) == BOOT_REPLACING)
181218 }
182219 }
183220
221+ private void processRemovedNodes (Set <InetAddressAndPort > removed )
222+ {
223+ for (InetAddressAndPort remove : removed )
224+ {
225+ GossipHelper .removeAndEvict (remove );
226+ PeersTable .removeFromSystemPeersTables (remove );
227+ }
228+ }
229+
230+ private void logCompletedReplacement (Directory directory , BootstrapAndReplace sequence )
231+ {
232+ // legacy log message for compatibility (& tests)
233+ InetAddressAndPort replaced = directory .endpoint (sequence .startReplace .replaced ());
234+ InetAddressAndPort replacement = directory .endpoint (sequence .startReplace .replacement ());
235+ Collection <Token > tokens = GossipHelper .getTokensFromOperation (sequence );
236+ logger .info ("Node {} will complete replacement of {} for tokens {}" , replacement , replaced , tokens );
237+ if (!replacement .equals (replaced ))
238+ {
239+ for (Token token : tokens )
240+ logger .warn ("Token {} changing ownership from {} to {}" , token , replaced , replacement );
241+ }
242+ }
243+
184244 private boolean directoryEntryChangedFor (NodeId nodeId , Directory prev , Directory next )
185245 {
186246 return prev .peerState (nodeId ) != next .peerState (nodeId ) ||
0 commit comments