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

zhangduo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/master by this push:
     new e45b9c42d51 HBASE-28180 Review the usage of 
RegionStates.getOrCreateServer (#5486)
e45b9c42d51 is described below

commit e45b9c42d517fc131e60bf76383e55975ded36b1
Author: Duo Zhang <zhang...@apache.org>
AuthorDate: Sat Dec 16 16:28:50 2023 +0800

    HBASE-28180 Review the usage of RegionStates.getOrCreateServer (#5486)
    
    Signed-off-by: Xiaolin Ha <haxiao...@apache.org>
---
 .../org/apache/hadoop/hbase/master/HMaster.java    |  12 ++
 .../hadoop/hbase/master/RegionServerTracker.java   |   2 +-
 .../apache/hadoop/hbase/master/ServerManager.java  |   5 +
 .../hbase/master/assignment/AssignmentManager.java | 155 +++++++++++++--------
 .../hbase/master/assignment/RegionStates.java      |  33 +++--
 .../hbase/master/assignment/ServerStateNode.java   |   1 +
 .../master/procedure/HBCKServerCrashProcedure.java |   2 +
 .../apache/hadoop/hbase/master/TestBalancer.java   |   2 +-
 .../hbase/master/TestClockSkewDetection.java       |  23 ++-
 .../hbase/master/TestClusterRestartFailover.java   |  37 +++--
 .../procedure/MasterProcedureTestingUtility.java   |   5 +
 .../procedure/TestServerRemoteProcedure.java       |   2 +-
 12 files changed, 191 insertions(+), 88 deletions(-)

diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
index b492b177e42..8567f00cad0 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
@@ -3342,6 +3342,18 @@ public class HMaster extends 
HBaseServerBase<MasterRpcServices> implements Maste
     procedureExecutor.getEnvironment().setEventReady(initialized, 
isInitialized);
   }
 
+  /**
+   * Mainly used in procedure related tests, where we will restart 
ProcedureExecutor and
+   * AssignmentManager, but we do not want to restart master(to speed up the 
test), so we need to
+   * disable rpc for a while otherwise some critical rpc requests such as
+   * reportRegionStateTransition could fail and cause region server to abort.
+   */
+  @RestrictedApi(explanation = "Should only be called in tests", link = "",
+      allowedOnPath = ".*/src/test/.*")
+  public void setServiceStarted(boolean started) {
+    this.serviceStarted = started;
+  }
+
   @Override
   public ProcedureEvent<?> getInitializedEvent() {
     return initialized;
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/RegionServerTracker.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/RegionServerTracker.java
index 71ca500a045..a09f6689a5c 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/RegionServerTracker.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/RegionServerTracker.java
@@ -135,7 +135,7 @@ public class RegionServerTracker extends ZKListener {
       .forEach(s -> LOG.error("{} has no matching ServerCrashProcedure", s));
     // create ServerNode for all possible live servers from wal directory and 
master local region
     liveServersBeforeRestart
-      .forEach(sn -> 
server.getAssignmentManager().getRegionStates().getOrCreateServer(sn));
+      .forEach(sn -> 
server.getAssignmentManager().getRegionStates().createServer(sn));
     ServerManager serverManager = server.getServerManager();
     synchronized (this) {
       Set<ServerName> liveServers = regionServers;
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/ServerManager.java 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/ServerManager.java
index 6a169beb53b..2afd48c58df 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/ServerManager.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/ServerManager.java
@@ -426,6 +426,7 @@ public class ServerManager {
   void recordNewServerWithLock(final ServerName serverName, final 
ServerMetrics sl) {
     LOG.info("Registering regionserver=" + serverName);
     this.onlineServers.put(serverName, sl);
+    master.getAssignmentManager().getRegionStates().createServer(serverName);
   }
 
   public ConcurrentNavigableMap<byte[], Long> getFlushedSequenceIdByRegion() {
@@ -603,6 +604,10 @@ public class ServerManager {
     }
     LOG.info("Processing expiration of " + serverName + " on " + 
this.master.getServerName());
     long pid = master.getAssignmentManager().submitServerCrash(serverName, 
true, force);
+    if (pid == Procedure.NO_PROC_ID) {
+      // skip later processing as we failed to submit SCP
+      return Procedure.NO_PROC_ID;
+    }
     storage.expired(serverName);
     // Tell our listeners that a server was removed
     if (!this.listeners.isEmpty()) {
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java
index 474b95a2a69..49cf29ee61d 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java
@@ -333,6 +333,12 @@ public class AssignmentManager {
               regionNode.getProcedure().stateLoaded(this, regionNode);
             }
             if (regionLocation != null) {
+              // TODO: this could lead to some orphan server state nodes, as 
it is possible that the
+              // region server is already dead and its SCP has already 
finished but we have
+              // persisted an opening state on this region server. Finally the 
TRSP will assign the
+              // region to another region server, so it will not cause 
critical problems, just waste
+              // some memory as no one will try to cleanup these orphan server 
state nodes.
+              regionStates.createServer(regionLocation);
               regionStates.addRegionToServer(regionNode);
             }
             if (RegionReplicaUtil.isDefaultReplica(regionInfo.getReplicaId())) 
{
@@ -1110,7 +1116,7 @@ public class AssignmentManager {
   // RS Region Transition Report helpers
   // 
============================================================================================
   private void 
reportRegionStateTransition(ReportRegionStateTransitionResponse.Builder builder,
-    ServerName serverName, List<RegionStateTransition> transitionList) throws 
IOException {
+    ServerStateNode serverNode, List<RegionStateTransition> transitionList) 
throws IOException {
     for (RegionStateTransition transition : transitionList) {
       switch (transition.getTransitionCode()) {
         case OPENED:
@@ -1120,7 +1126,7 @@ public class AssignmentManager {
           final RegionInfo hri = 
ProtobufUtil.toRegionInfo(transition.getRegionInfo(0));
           long procId =
             transition.getProcIdCount() > 0 ? transition.getProcId(0) : 
Procedure.NO_PROC_ID;
-          updateRegionTransition(serverName, transition.getTransitionCode(), 
hri,
+          updateRegionTransition(serverNode, transition.getTransitionCode(), 
hri,
             transition.hasOpenSeqNum() ? transition.getOpenSeqNum() : 
HConstants.NO_SEQNUM, procId);
           break;
         case READY_TO_SPLIT:
@@ -1130,7 +1136,7 @@ public class AssignmentManager {
           final RegionInfo parent = 
ProtobufUtil.toRegionInfo(transition.getRegionInfo(0));
           final RegionInfo splitA = 
ProtobufUtil.toRegionInfo(transition.getRegionInfo(1));
           final RegionInfo splitB = 
ProtobufUtil.toRegionInfo(transition.getRegionInfo(2));
-          updateRegionSplitTransition(serverName, 
transition.getTransitionCode(), parent, splitA,
+          updateRegionSplitTransition(serverNode, 
transition.getTransitionCode(), parent, splitA,
             splitB);
           break;
         case READY_TO_MERGE:
@@ -1140,7 +1146,7 @@ public class AssignmentManager {
           final RegionInfo merged = 
ProtobufUtil.toRegionInfo(transition.getRegionInfo(0));
           final RegionInfo mergeA = 
ProtobufUtil.toRegionInfo(transition.getRegionInfo(1));
           final RegionInfo mergeB = 
ProtobufUtil.toRegionInfo(transition.getRegionInfo(2));
-          updateRegionMergeTransition(serverName, 
transition.getTransitionCode(), merged, mergeA,
+          updateRegionMergeTransition(serverNode, 
transition.getTransitionCode(), merged, mergeA,
             mergeB);
           break;
       }
@@ -1152,7 +1158,12 @@ public class AssignmentManager {
     ReportRegionStateTransitionResponse.Builder builder =
       ReportRegionStateTransitionResponse.newBuilder();
     ServerName serverName = ProtobufUtil.toServerName(req.getServer());
-    ServerStateNode serverNode = regionStates.getOrCreateServer(serverName);
+    ServerStateNode serverNode = regionStates.getServerNode(serverName);
+    if (serverNode == null) {
+      LOG.warn("No server node for {}", serverName);
+      builder.setErrorMessage("No server node for " + serverName);
+      return builder.build();
+    }
     // here we have to acquire a read lock instead of a simple exclusive lock. 
This is because that
     // we should not block other reportRegionStateTransition call from the 
same region server. This
     // is not only about performance, but also to prevent dead lock. Think of 
the meta region is
@@ -1165,7 +1176,7 @@ public class AssignmentManager {
       // above in submitServerCrash method and HBASE-21508 for more details.
       if (serverNode.isInState(ServerState.ONLINE)) {
         try {
-          reportRegionStateTransition(builder, serverName, 
req.getTransitionList());
+          reportRegionStateTransition(builder, serverNode, 
req.getTransitionList());
         } catch (PleaseHoldException e) {
           LOG.trace("Failed transition ", e);
           throw e;
@@ -1187,7 +1198,7 @@ public class AssignmentManager {
     return builder.build();
   }
 
-  private void updateRegionTransition(ServerName serverName, TransitionCode 
state,
+  private void updateRegionTransition(ServerStateNode serverNode, 
TransitionCode state,
     RegionInfo regionInfo, long seqId, long procId) throws IOException {
     checkMetaLoaded(regionInfo);
 
@@ -1195,13 +1206,12 @@ public class AssignmentManager {
     if (regionNode == null) {
       // the table/region is gone. maybe a delete, split, merge
       throw new UnexpectedStateException(String.format(
-        "Server %s was trying to transition region %s to %s. but Region is not 
known.", serverName,
-        regionInfo, state));
+        "Server %s was trying to transition region %s to %s. but Region is not 
known.",
+        serverNode.getServerName(), regionInfo, state));
     }
-    LOG.trace("Update region transition serverName={} region={} 
regionState={}", serverName,
-      regionNode, state);
+    LOG.trace("Update region transition serverName={} region={} 
regionState={}",
+      serverNode.getServerName(), regionNode, state);
 
-    ServerStateNode serverNode = regionStates.getOrCreateServer(serverName);
     regionNode.lock();
     try {
       if (!reportTransition(regionNode, serverNode, state, seqId, procId)) {
@@ -1218,8 +1228,8 @@ public class AssignmentManager {
         ) {
           LOG.info("RegionServer {} {}", state, 
regionNode.getRegionInfo().getEncodedName());
         } else {
-          LOG.warn("No matching procedure found for {} transition on {} to 
{}", serverName,
-            regionNode, state);
+          LOG.warn("No matching procedure found for {} transition on {} to {}",
+            serverNode.getServerName(), regionNode, state);
         }
       }
     } finally {
@@ -1239,8 +1249,9 @@ public class AssignmentManager {
     return true;
   }
 
-  private void updateRegionSplitTransition(final ServerName serverName, final 
TransitionCode state,
-    final RegionInfo parent, final RegionInfo hriA, final RegionInfo hriB) 
throws IOException {
+  private void updateRegionSplitTransition(final ServerStateNode serverNode,
+    final TransitionCode state, final RegionInfo parent, final RegionInfo hriA,
+    final RegionInfo hriB) throws IOException {
     checkMetaLoaded(parent);
 
     if (state != TransitionCode.READY_TO_SPLIT) {
@@ -1264,8 +1275,8 @@ public class AssignmentManager {
     // Submit the Split procedure
     final byte[] splitKey = hriB.getStartKey();
     if (LOG.isDebugEnabled()) {
-      LOG.debug("Split request from " + serverName + ", parent=" + parent + " 
splitKey="
-        + Bytes.toStringBinary(splitKey));
+      LOG.debug("Split request from {}, parent={}, splitKey={}", 
serverNode.getServerName(), parent,
+        Bytes.toStringBinary(splitKey));
     }
     // Processing this report happens asynchronously from other activities 
which can mutate
     // the region state. For example, a split procedure may already be running 
for this parent.
@@ -1279,21 +1290,22 @@ public class AssignmentManager {
     if (parentState != null && parentState.isOpened()) {
       
master.getMasterProcedureExecutor().submitProcedure(createSplitProcedure(parent,
 splitKey));
     } else {
-      LOG.info("Ignoring split request from " + serverName + ", parent=" + 
parent
-        + " because parent is unknown or not open");
+      LOG.info("Ignoring split request from {}, parent={} because parent is 
unknown or not open",
+        serverNode.getServerName(), parent);
       return;
     }
 
     // If the RS is < 2.0 throw an exception to abort the operation, we are 
handling the split
-    if (master.getServerManager().getVersionNumber(serverName) < 0x0200000) {
+    if (master.getServerManager().getVersionNumber(serverNode.getServerName()) 
< 0x0200000) {
       throw new UnsupportedOperationException(
         String.format("Split handled by the master: " + "parent=%s hriA=%s 
hriB=%s",
           parent.getShortNameToLog(), hriA, hriB));
     }
   }
 
-  private void updateRegionMergeTransition(final ServerName serverName, final 
TransitionCode state,
-    final RegionInfo merged, final RegionInfo hriA, final RegionInfo hriB) 
throws IOException {
+  private void updateRegionMergeTransition(final ServerStateNode serverNode,
+    final TransitionCode state, final RegionInfo merged, final RegionInfo hriA,
+    final RegionInfo hriB) throws IOException {
     checkMetaLoaded(merged);
 
     if (state != TransitionCode.READY_TO_MERGE) {
@@ -1315,7 +1327,7 @@ public class AssignmentManager {
     
master.getMasterProcedureExecutor().submitProcedure(createMergeProcedure(hriA, 
hriB));
 
     // If the RS is < 2.0 throw an exception to abort the operation, we are 
handling the merge
-    if (master.getServerManager().getVersionNumber(serverName) < 0x0200000) {
+    if (master.getServerManager().getVersionNumber(serverNode.getServerName()) 
< 0x0200000) {
       throw new UnsupportedOperationException(
         String.format("Merge not handled yet: regionState=%s merged=%s hriA=%s 
hriB=%s", state,
           merged, hriA, hriB));
@@ -1345,12 +1357,19 @@ public class AssignmentManager {
         
regionNames.stream().map(Bytes::toStringBinary).collect(Collectors.toList()));
     }
 
-    ServerStateNode serverNode = regionStates.getOrCreateServer(serverName);
-    synchronized (serverNode) {
+    ServerStateNode serverNode = regionStates.getServerNode(serverName);
+    if (serverNode == null) {
+      LOG.warn("Got a report from server {} where its server node is null", 
serverName);
+      return;
+    }
+    serverNode.readLock().lock();
+    try {
       if (!serverNode.isInState(ServerState.ONLINE)) {
-        LOG.warn("Got a report from a server result in state " + 
serverNode.getState());
+        LOG.warn("Got a report from a server result in state {}", serverNode);
         return;
       }
+    } finally {
+      serverNode.readLock().unlock();
     }
 
     // Track the regionserver reported online regions in memory.
@@ -1756,6 +1775,12 @@ public class AssignmentManager {
         localState.matches(State.OPEN, State.OPENING, State.CLOSING, 
State.SPLITTING, State.MERGING)
       ) {
         assert regionLocation != null : "found null region location for " + 
regionNode;
+        // TODO: this could lead to some orphan server state nodes, as it is 
possible that the
+        // region server is already dead and its SCP has already finished but 
we have
+        // persisted an opening state on this region server. Finally the TRSP 
will assign the
+        // region to another region server, so it will not cause critical 
problems, just waste
+        // some memory as no one will try to cleanup these orphan server state 
nodes.
+        regionStates.createServer(regionLocation);
         regionStates.addRegionToServer(regionNode);
       } else if (localState == State.OFFLINE || regionInfo.isOffline()) {
         regionStates.addToOfflineRegions(regionNode);
@@ -1841,7 +1866,17 @@ public class AssignmentManager {
     synchronized (rsReports) {
       rsReports.remove(serverName);
     }
+    if (serverNode == null) {
+      if (force) {
+        LOG.info("Force adding ServerCrashProcedure for {} when server node is 
null", serverName);
+      } else {
+        // for normal case, do not schedule SCP if ServerStateNode is null
+        LOG.warn("Skip adding ServerCrashProcedure for {} because server node 
is null", serverName);
+        return Procedure.NO_PROC_ID;
+      }
+    }
 
+    ProcedureExecutor<MasterProcedureEnv> procExec = 
this.master.getMasterProcedureExecutor();
     // We hold the write lock here for fencing on reportRegionStateTransition. 
Once we set the
     // server state to CRASHED, we will no longer accept the 
reportRegionStateTransition call from
     // this server. This is used to simplify the implementation for TRSP and 
SCP, where we can make
@@ -1849,44 +1884,42 @@ public class AssignmentManager {
     if (serverNode != null) {
       serverNode.writeLock().lock();
     }
-    boolean carryingMeta;
-    long pid;
     try {
-      ProcedureExecutor<MasterProcedureEnv> procExec = 
this.master.getMasterProcedureExecutor();
-      carryingMeta = isCarryingMeta(serverName);
-      if (!force && serverNode != null && 
!serverNode.isInState(ServerState.ONLINE)) {
-        LOG.info("Skip adding ServerCrashProcedure for {} (meta={}) -- 
running?", serverNode,
-          carryingMeta);
-        return Procedure.NO_PROC_ID;
-      } else {
-        MasterProcedureEnv mpe = procExec.getEnvironment();
-        // If serverNode == null, then 'Unknown Server'. Schedule HBCKSCP 
instead.
-        // HBCKSCP scours Master in-memory state AND hbase;meta for references 
to
-        // serverName just-in-case. An SCP that is scheduled when the server is
-        // 'Unknown' probably originated externally with HBCK2 fix-it tool.
-        ServerState oldState = null;
-        if (serverNode != null) {
-          oldState = serverNode.getState();
-          serverNode.setState(ServerState.CRASHED);
-        }
 
+      boolean carryingMeta = isCarryingMeta(serverName);
+      if (serverNode != null && !serverNode.isInState(ServerState.ONLINE)) {
         if (force) {
-          pid = procExec.submitProcedure(
-            new HBCKServerCrashProcedure(mpe, serverName, shouldSplitWal, 
carryingMeta));
+          LOG.info("Force adding ServerCrashProcedure for {} (meta={}) when 
state is not {}",
+            serverNode, carryingMeta, ServerState.ONLINE);
         } else {
-          pid = procExec.submitProcedure(
-            new ServerCrashProcedure(mpe, serverName, shouldSplitWal, 
carryingMeta));
+          LOG.info("Skip adding ServerCrashProcedure for {} (meta={}) when 
state is not {}",
+            serverNode, carryingMeta, ServerState.ONLINE);
+          return Procedure.NO_PROC_ID;
         }
-        LOG.info("Scheduled ServerCrashProcedure pid={} for {} 
(carryingMeta={}){}.", pid,
-          serverName, carryingMeta,
-          serverNode == null ? "" : " " + serverNode.toString() + ", 
oldState=" + oldState);
       }
+      MasterProcedureEnv mpe = procExec.getEnvironment();
+      // If serverNode == null, then 'Unknown Server'. Schedule HBCKSCP 
instead.
+      // HBCKSCP scours Master in-memory state AND hbase;meta for references to
+      // serverName just-in-case. An SCP that is scheduled when the server is
+      // 'Unknown' probably originated externally with HBCK2 fix-it tool.
+      ServerState oldState = null;
+      if (serverNode != null) {
+        oldState = serverNode.getState();
+        serverNode.setState(ServerState.CRASHED);
+      }
+      ServerCrashProcedure scp = force
+        ? new HBCKServerCrashProcedure(mpe, serverName, shouldSplitWal, 
carryingMeta)
+        : new ServerCrashProcedure(mpe, serverName, shouldSplitWal, 
carryingMeta);
+      long pid = procExec.submitProcedure(scp);
+      LOG.info("Scheduled ServerCrashProcedure pid={} for {} 
(carryingMeta={}){}.", pid, serverName,
+        carryingMeta,
+        serverNode == null ? "" : " " + serverNode.toString() + ", oldState=" 
+ oldState);
+      return pid;
     } finally {
       if (serverNode != null) {
         serverNode.writeLock().unlock();
       }
     }
-    return pid;
   }
 
   public void offlineRegion(final RegionInfo regionInfo) {
@@ -2015,7 +2048,18 @@ public class AssignmentManager {
     // update the region state, which means that the region could be in any 
state when we want to
     // assign it after a RS crash. So here we do not pass the expectedStates 
parameter.
     return transitStateAndUpdate(regionNode, State.OPENING).thenAccept(r -> {
-      regionStates.addRegionToServer(regionNode);
+      ServerStateNode serverNode = 
regionStates.getServerNode(regionNode.getRegionLocation());
+      // Here the server node could be null. For example, we want to assign 
the region to a given
+      // region server and it crashes, and it is the region server which holds 
hbase:meta, then the
+      // above transitStateAndUpdate call will never succeed until we finishes 
the SCP for it. But
+      // after the SCP finishes, the server node will be removed, so when we 
arrive there, the
+      // server
+      // node will be null. This is not a big problem if we skip adding it, as 
later we will fail to
+      // execute the remote procedure on the region server and then try to 
assign to another region
+      // server
+      if (serverNode != null) {
+        serverNode.addRegion(regionNode);
+      }
       // update the operation count metrics
       metrics.incrementOperationCounter();
     });
@@ -2059,7 +2103,6 @@ public class AssignmentManager {
         if (isMetaRegion(hri)) {
           setMetaAssigned(hri, false);
         }
-        regionStates.addRegionToServer(regionNode);
         // update the operation count metrics
         metrics.incrementOperationCounter();
       });
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionStates.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionStates.java
index a0f0ba9ab80..b3de79c1826 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionStates.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionStates.java
@@ -405,7 +405,7 @@ public class RegionStates {
   // 
============================================================================================
 
   private void setServerState(ServerName serverName, ServerState state) {
-    ServerStateNode serverNode = getOrCreateServer(serverName);
+    ServerStateNode serverNode = getServerNode(serverName);
     synchronized (serverNode) {
       serverNode.setState(state);
     }
@@ -746,18 +746,16 @@ public class RegionStates {
   // ==========================================================================
 
   /**
-   * Be judicious calling this method. Do it on server register ONLY otherwise 
you could mess up
-   * online server accounting. TOOD: Review usage and convert to {@link 
#getServerNode(ServerName)}
-   * where we can.
+   * Create the ServerStateNode when registering a new region server
    */
-  public ServerStateNode getOrCreateServer(final ServerName serverName) {
-    return serverMap.computeIfAbsent(serverName, key -> new 
ServerStateNode(key));
+  public void createServer(ServerName serverName) {
+    serverMap.computeIfAbsent(serverName, key -> new ServerStateNode(key));
   }
 
   /**
    * Called by SCP at end of successful processing.
    */
-  public void removeServer(final ServerName serverName) {
+  public void removeServer(ServerName serverName) {
     serverMap.remove(serverName);
   }
 
@@ -776,17 +774,24 @@ public class RegionStates {
     return numServers == 0 ? 0.0 : (double) totalLoad / (double) numServers;
   }
 
-  public ServerStateNode addRegionToServer(final RegionStateNode regionNode) {
-    ServerStateNode serverNode = 
getOrCreateServer(regionNode.getRegionLocation());
+  public void addRegionToServer(final RegionStateNode regionNode) {
+    ServerStateNode serverNode = getServerNode(regionNode.getRegionLocation());
     serverNode.addRegion(regionNode);
-    return serverNode;
   }
 
-  public ServerStateNode removeRegionFromServer(final ServerName serverName,
+  public void removeRegionFromServer(final ServerName serverName,
     final RegionStateNode regionNode) {
-    ServerStateNode serverNode = getOrCreateServer(serverName);
-    serverNode.removeRegion(regionNode);
-    return serverNode;
+    ServerStateNode serverNode = getServerNode(serverName);
+    // here the server node could be null. For example, if there is already a 
TRSP for a region and
+    // at the same time, the target server is crashed and there is a SCP. The 
SCP will interrupt the
+    // TRSP and the TRSP will first set the region as abnormally closed and 
remove it from the
+    // server node. But here, this TRSP is not a child procedure of the SCP, 
so it is possible that
+    // the SCP finishes, thus removes the server node for this region server, 
before the TRSP wakes
+    // up and enter here to remove the region node from the server node, then 
we will get a null
+    // server node here.
+    if (serverNode != null) {
+      serverNode.removeRegion(regionNode);
+    }
   }
 
   // ==========================================================================
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/ServerStateNode.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/ServerStateNode.java
index 3bde01ad5f5..06eea30370f 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/ServerStateNode.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/ServerStateNode.java
@@ -35,6 +35,7 @@ import org.apache.yetus.audience.InterfaceAudience;
 public class ServerStateNode implements Comparable<ServerStateNode> {
   private final Set<RegionStateNode> regions;
   private final ServerName serverName;
+  // the lock here is for fencing SCP and TRSP, so not all operations need to 
hold this lock
   private final ReadWriteLock lock = new ReentrantReadWriteLock();
   private volatile ServerState state = ServerState.ONLINE;
 
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/HBCKServerCrashProcedure.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/HBCKServerCrashProcedure.java
index bd8f95973eb..43d69361c2d 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/HBCKServerCrashProcedure.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/HBCKServerCrashProcedure.java
@@ -105,6 +105,8 @@ public class HBCKServerCrashProcedure extends 
ServerCrashProcedure {
       LOG.warn("Failed scan of hbase:meta for 'Unknown Servers'", ioe);
       return ris;
     }
+    // create the server state node too
+    env.getAssignmentManager().getRegionStates().createServer(getServerName());
     LOG.info("Found {} mentions of {} in hbase:meta of OPEN/OPENING Regions: 
{}",
       visitor.getReassigns().size(), getServerName(), 
visitor.getReassigns().stream()
         .map(RegionInfo::getEncodedName).collect(Collectors.joining(",")));
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestBalancer.java 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestBalancer.java
index 8870c0f6dc3..d5ff71000a8 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestBalancer.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestBalancer.java
@@ -78,7 +78,7 @@ public class TestBalancer {
     AssignmentManager assignmentManager = master.getAssignmentManager();
     RegionStates regionStates = assignmentManager.getRegionStates();
     ServerName sn1 = 
ServerName.parseServerName("asf903.gq1.ygridcore.net,52690,1517835491385");
-    regionStates.getOrCreateServer(sn1);
+    regionStates.createServer(sn1);
 
     TableStateManager tableStateManager = master.getTableStateManager();
     ServerManager serverManager = master.getServerManager();
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClockSkewDetection.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClockSkewDetection.java
index 13b1f5fea61..2d5d9a4bc18 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClockSkewDetection.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClockSkewDetection.java
@@ -18,12 +18,16 @@
 package org.apache.hadoop.hbase.master;
 
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.net.InetAddress;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.ClockOutOfSyncException;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
 import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
+import org.apache.hadoop.hbase.master.assignment.RegionStates;
 import org.apache.hadoop.hbase.testclassification.MasterTests;
 import org.apache.hadoop.hbase.testclassification.SmallTests;
 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
@@ -44,11 +48,28 @@ public class TestClockSkewDetection {
 
   private static final Logger LOG = 
LoggerFactory.getLogger(TestClockSkewDetection.class);
 
+  private static final class DummyMasterServices extends 
MockNoopMasterServices {
+
+    private final AssignmentManager am;
+
+    public DummyMasterServices(Configuration conf) {
+      super(conf);
+      am = mock(AssignmentManager.class);
+      RegionStates rss = mock(RegionStates.class);
+      when(am.getRegionStates()).thenReturn(rss);
+    }
+
+    @Override
+    public AssignmentManager getAssignmentManager() {
+      return am;
+    }
+  }
+
   @Test
   public void testClockSkewDetection() throws Exception {
     final Configuration conf = HBaseConfiguration.create();
     ServerManager sm =
-      new ServerManager(new MockNoopMasterServices(conf), new 
DummyRegionServerList());
+      new ServerManager(new DummyMasterServices(conf), new 
DummyRegionServerList());
 
     LOG.debug("regionServerStartup 1");
     InetAddress ia1 = InetAddress.getLocalHost();
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClusterRestartFailover.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClusterRestartFailover.java
index 69d81a986fe..64ca7521792 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClusterRestartFailover.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClusterRestartFailover.java
@@ -17,7 +17,7 @@
  */
 package org.apache.hadoop.hbase.master;
 
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -33,6 +33,7 @@ import org.apache.hadoop.hbase.HBaseClassTestRule;
 import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.StartTestingClusterOption;
 import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.Waiter.ExplainingPredicate;
 import org.apache.hadoop.hbase.client.RegionInfo;
 import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
@@ -105,36 +106,44 @@ public class TestClusterRestartFailover extends 
AbstractTestRestartCluster {
     UTIL.waitFor(60000, () -> 
UTIL.getHBaseCluster().getMaster().isInitialized());
     LOG.info("Started cluster master, waiting for {}", SERVER_FOR_TEST);
     UTIL.waitFor(60000, () -> getServerStateNode(SERVER_FOR_TEST) != null);
-    serverNode = getServerStateNode(SERVER_FOR_TEST);
-    assertFalse("serverNode should not be ONLINE during SCP processing",
-      serverNode.isInState(ServerState.ONLINE));
+    UTIL.waitFor(30000, new ExplainingPredicate<Exception>() {
+
+      @Override
+      public boolean evaluate() throws Exception {
+        return 
!getServerStateNode(SERVER_FOR_TEST).isInState(ServerState.ONLINE);
+      }
+
+      @Override
+      public String explainFailure() throws Exception {
+        return "serverNode should not be ONLINE during SCP processing";
+      }
+    });
     Optional<Procedure<?>> procedure = 
UTIL.getHBaseCluster().getMaster().getProcedures().stream()
       .filter(p -> (p instanceof ServerCrashProcedure)
         && ((ServerCrashProcedure) p).getServerName().equals(SERVER_FOR_TEST))
       .findAny();
     assertTrue("Should have one SCP for " + SERVER_FOR_TEST, 
procedure.isPresent());
-    assertTrue("Submit the SCP for the same serverName " + SERVER_FOR_TEST + " 
which should fail",
-      
UTIL.getHBaseCluster().getMaster().getServerManager().expireServer(SERVER_FOR_TEST)
-          == Procedure.NO_PROC_ID);
+    assertEquals("Submit the SCP for the same serverName " + SERVER_FOR_TEST + 
" which should fail",
+      Procedure.NO_PROC_ID,
+      
UTIL.getHBaseCluster().getMaster().getServerManager().expireServer(SERVER_FOR_TEST));
 
     // Wait the SCP to finish
     LOG.info("Waiting on latch");
     SCP_LATCH.countDown();
     UTIL.waitFor(60000, () -> procedure.get().isFinished());
+    assertNull("serverNode should be deleted after SCP finished",
+      getServerStateNode(SERVER_FOR_TEST));
 
-    assertFalse(
+    assertEquals(
       "Even when the SCP is finished, the duplicate SCP should not be 
scheduled for "
         + SERVER_FOR_TEST,
-      
UTIL.getHBaseCluster().getMaster().getServerManager().expireServer(SERVER_FOR_TEST)
-          == Procedure.NO_PROC_ID);
-    serverNode = 
UTIL.getHBaseCluster().getMaster().getAssignmentManager().getRegionStates()
-      .getServerNode(SERVER_FOR_TEST);
-    assertNull("serverNode should be deleted after SCP finished", serverNode);
+      Procedure.NO_PROC_ID,
+      
UTIL.getHBaseCluster().getMaster().getServerManager().expireServer(SERVER_FOR_TEST));
 
     MetricsMasterSource masterSource =
       UTIL.getHBaseCluster().getMaster().getMasterMetrics().getMetricsSource();
     metricsHelper.assertCounter(MetricsMasterSource.SERVER_CRASH_METRIC_PREFIX 
+ "SubmittedCount",
-      4, masterSource);
+      3, masterSource);
   }
 
   private void setupCluster() throws Exception {
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/MasterProcedureTestingUtility.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/MasterProcedureTestingUtility.java
index 8b7bd8d92e7..9456849bd37 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/MasterProcedureTestingUtility.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/MasterProcedureTestingUtility.java
@@ -87,6 +87,7 @@ public class MasterProcedureTestingUtility {
       new Callable<Void>() {
         @Override
         public Void call() throws Exception {
+          master.setServiceStarted(false);
           AssignmentManager am = env.getAssignmentManager();
           // try to simulate a master restart by removing the ServerManager 
states about seqIDs
           for (RegionState regionState : 
am.getRegionStates().getRegionStates()) {
@@ -109,6 +110,10 @@ public class MasterProcedureTestingUtility {
           am.setupRIT(procExec.getActiveProceduresNoCopy().stream().filter(p 
-> !p.isSuccess())
             .filter(p -> p instanceof TransitRegionStateProcedure)
             .map(p -> (TransitRegionStateProcedure) 
p).collect(Collectors.toList()));
+          // create server state node, to simulate master start up
+          env.getMasterServices().getServerManager().getOnlineServersList()
+            .forEach(am.getRegionStates()::createServer);
+          master.setServiceStarted(true);
           return null;
         }
       },
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestServerRemoteProcedure.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestServerRemoteProcedure.java
index f482c5441d6..b7c2d509987 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestServerRemoteProcedure.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestServerRemoteProcedure.java
@@ -88,7 +88,7 @@ public class TestServerRemoteProcedure {
     master.start(2, rsDispatcher);
     am = master.getAssignmentManager();
     master.getServerManager().getOnlineServersList().stream()
-      .forEach(serverName -> 
am.getRegionStates().getOrCreateServer(serverName));
+      .forEach(serverName -> am.getRegionStates().createServer(serverName));
   }
 
   @After


Reply via email to