diff --git a/hbase-procedure/src/main/java/org/apache/hadoop/hbase/procedure2/LockedResourceType.java b/hbase-procedure/src/main/java/org/apache/hadoop/hbase/procedure2/LockedResourceType.java index dc9b5d428df..55d195b3920 100644 --- a/hbase-procedure/src/main/java/org/apache/hadoop/hbase/procedure2/LockedResourceType.java +++ b/hbase-procedure/src/main/java/org/apache/hadoop/hbase/procedure2/LockedResourceType.java @@ -22,5 +22,5 @@ import org.apache.yetus.audience.InterfaceAudience; @InterfaceAudience.Private public enum LockedResourceType { - SERVER, NAMESPACE, TABLE, REGION, PEER + SERVER, NAMESPACE, TABLE, REGION, PEER, META } 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 0871482a5a0..f20cc61d923 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 @@ -910,7 +910,7 @@ public class HMaster extends HRegionServer implements MasterServices { // Bring up hbase:meta. recoverMeta is a blocking call waiting until hbase:meta is deployed. // It also starts the TableStateManager. - MasterMetaBootstrap metaBootstrap = createMetaBootstrap(this, status); + MasterMetaBootstrap metaBootstrap = createMetaBootstrap(); metaBootstrap.recoverMeta(); //Initialize after meta as it scans meta @@ -1055,12 +1055,18 @@ public class HMaster extends HRegionServer implements MasterServices { } /** + *
* Create a {@link MasterMetaBootstrap} instance. + *
+ *+ * Will be overridden in tests. + *
*/ - MasterMetaBootstrap createMetaBootstrap(final HMaster master, final MonitoredTask status) { + @VisibleForTesting + protected MasterMetaBootstrap createMetaBootstrap() { // We put this out here in a method so can do a Mockito.spy and stub it out // w/ a mocked up MasterMetaBootstrap. - return new MasterMetaBootstrap(master, status); + return new MasterMetaBootstrap(this); } /** @@ -3161,7 +3167,8 @@ public class HMaster extends HRegionServer implements MasterServices { cpHost.preGetLocks(); } - MasterProcedureScheduler procedureScheduler = procedureExecutor.getEnvironment().getProcedureScheduler(); + MasterProcedureScheduler procedureScheduler = + procedureExecutor.getEnvironment().getProcedureScheduler(); final List* This is a very particular check. The {@link org.apache.hadoop.hbase.master.ServerManager} is - * where you go to check on state of 'Servers', what Servers are online, etc. Here we are - * checking the state of a server that is post expiration, a ServerManager function that moves a - * server from online to dead. Here we are seeing if the server has moved beyond a particular - * point in the recovery process such that it is safe to move on with assigns; etc. - * @return True if this Server does not exist or if does and it is marked as OFFLINE (which - * happens after all WALs have been split on this server making it so assigns, etc. can - * proceed). If null, presumes the ServerStateNode was cleaned up by SCP. + * where you go to check on state of 'Servers', what Servers are online, etc. + *
+ *+ * Here we are checking the state of a server that is post expiration, a ServerManager function + * that moves a server from online to dead. Here we are seeing if the server has moved beyond a + * particular point in the recovery process such that it is safe to move on with assigns; etc. + *
+ *+ * For now it is only used in + * {@link UnassignProcedure#remoteCallFailed(MasterProcedureEnv, RegionStateNode, IOException)} to + * see whether we can safely quit without losing data. + *
+ * @param meta whether to check for meta log splitting + * @return {@code true} if the server does not exist or the log splitting is done, i.e, the server + * is in OFFLINE state, or for meta log, is in SPLITTING_META_DONE state. If null, + * presumes the ServerStateNode was cleaned up by SCP. + * @see UnassignProcedure#remoteCallFailed(MasterProcedureEnv, RegionStateNode, IOException) */ - boolean isDeadServerProcessed(final ServerName serverName) { + boolean isLogSplittingDone(ServerName serverName, boolean meta) { ServerStateNode ssn = this.regionStates.getServerNode(serverName); if (ssn == null) { return true; } + ServerState[] inState = + meta + ? new ServerState[] { ServerState.SPLITTING_META_DONE, ServerState.SPLITTING, + ServerState.OFFLINE } + : new ServerState[] { ServerState.OFFLINE }; synchronized (ssn) { - return ssn.isOffline(); + return ssn.isInState(inState); } } 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 5f0578ed53a..26b340f56e3 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 @@ -317,13 +317,27 @@ public class RegionStates { */ ONLINE, + /** + * Only server which carries meta can have this state. We will split wal for meta and then + * assign meta first before splitting other wals. + */ + SPLITTING_META, + + /** + * Indicate that the meta splitting is done. We need this state so that the UnassignProcedure + * for meta can safely quit. See the comments in UnassignProcedure.remoteCallFailed for more + * details. + */ + SPLITTING_META_DONE, + /** * Server expired/crashed. Currently undergoing WAL splitting. */ SPLITTING, /** - * WAL splitting done. + * WAL splitting done. This state will be used to tell the UnassignProcedure that it can safely + * quit. See the comments in UnassignProcedure.remoteCallFailed for more details. */ OFFLINE } @@ -357,10 +371,6 @@ public class RegionStates { return reportEvent; } - public boolean isOffline() { - return this.state.equals(ServerState.OFFLINE); - } - public boolean isInState(final ServerState... expected) { boolean expectedState = false; if (expected != null) { @@ -371,7 +381,7 @@ public class RegionStates { return expectedState; } - public void setState(final ServerState state) { + private void setState(final ServerState state) { this.state = state; } @@ -612,18 +622,40 @@ public class RegionStates { } // ============================================================================================ - // TODO: split helpers + // Split helpers + // These methods will only be called in ServerCrashProcedure, and at the end of SCP we will remove + // the ServerStateNode by calling removeServer. // ============================================================================================ + private void setServerState(ServerName serverName, ServerState state) { + ServerStateNode serverNode = getOrCreateServer(serverName); + synchronized (serverNode) { + serverNode.setState(state); + } + } + /** - * Call this when we start log splitting a crashed Server. + * Call this when we start meta log splitting a crashed Server. + * @see #metaLogSplit(ServerName) + */ + public void metaLogSplitting(ServerName serverName) { + setServerState(serverName, ServerState.SPLITTING_META); + } + + /** + * Called after we've split the meta logs on a crashed Server. + * @see #metaLogSplitting(ServerName) + */ + public void metaLogSplit(ServerName serverName) { + setServerState(serverName, ServerState.SPLITTING_META_DONE); + } + + /** + * Call this when we start log splitting for a crashed Server. * @see #logSplit(ServerName) */ public void logSplitting(final ServerName serverName) { - final ServerStateNode serverNode = getOrCreateServer(serverName); - synchronized (serverNode) { - serverNode.setState(ServerState.SPLITTING); - } + setServerState(serverName, ServerState.SPLITTING); } /** @@ -631,17 +663,7 @@ public class RegionStates { * @see #logSplitting(ServerName) */ public void logSplit(final ServerName serverName) { - final ServerStateNode serverNode = getOrCreateServer(serverName); - synchronized (serverNode) { - serverNode.setState(ServerState.OFFLINE); - } - } - - public void logSplit(final RegionInfo regionInfo) { - final RegionStateNode regionNode = getRegionStateNode(regionInfo); - synchronized (regionNode) { - regionNode.setState(State.SPLIT); - } + setServerState(serverName, ServerState.OFFLINE); } @VisibleForTesting diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionTransitionProcedure.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionTransitionProcedure.java index 946bd3b2f01..b96fb20d2de 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionTransitionProcedure.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionTransitionProcedure.java @@ -213,7 +213,7 @@ public abstract class RegionTransitionProcedure RegionStateNode regionNode, IOException exception); @Override - public void remoteCallFailed(final MasterProcedureEnv env, + public synchronized void remoteCallFailed(final MasterProcedureEnv env, final ServerName serverName, final IOException exception) { final RegionStateNode regionNode = getRegionState(env); LOG.warn("Remote call failed {}; {}; {}; exception={}", serverName, diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/UnassignProcedure.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/UnassignProcedure.java index e2efdecd342..4f58a0f305c 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/UnassignProcedure.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/UnassignProcedure.java @@ -310,12 +310,29 @@ public class UnassignProcedure extends RegionTransitionProcedure { exception.getClass().getSimpleName()); if (!env.getMasterServices().getServerManager().expireServer(serverName)) { // Failed to queue an expire. Lots of possible reasons including it may be already expired. - // If so, is it beyond the state where we will be woken-up if go ahead and suspend the - // procedure. Look for this rare condition. - if (env.getAssignmentManager().isDeadServerProcessed(serverName)) { + // In ServerCrashProcedure and RecoverMetaProcedure, there is a handleRIT stage where we + // will iterator over all the RIT procedures for the related regions of a crashed RS and + // fail them with ServerCrashException. You can see the isSafeToProceed method above for + // more details. + // This can work for most cases, but since we do not hold the region lock in handleRIT, + // there could be race that we arrive here after the handleRIT stage of the SCP. So here we + // need to check whether it is safe to quit. + // Notice that, the first assumption is that we can only quit after the log splitting is + // done, as MRP can schedule an AssignProcedure right after us, and if the log splitting has + // not been done then there will be data loss. And in SCP, we will change the state from + // SPLITTING to OFFLINE(or SPLITTING_META_DONE for meta log processing) after finishing the + // log splitting, and then calling handleRIT, so checking the state here can be a safe + // fence. If the state is not OFFLINE(or SPLITTING_META_DONE), then we can just leave this + // procedure in suspended state as we can make sure that the handleRIT has not been executed + // yet and it will wake us up later. And if the state is OFFLINE(or SPLITTING_META_DONE), we + // can safely quit since there will be no data loss. There could be duplicated + // AssignProcedures for the same region but it is OK as we will do a check at the beginning + // of AssignProcedure to prevent double assign. And there we have region lock so there will + // be no race. + if (env.getAssignmentManager().isLogSplittingDone(serverName, isMeta())) { // Its ok to proceed with this unassign. - LOG.info("{} is dead and processed; moving procedure to finished state; {}", - serverName, this); + LOG.info("{} is dead and processed; moving procedure to finished state; {}", serverName, + this); proceed(env, regionNode); // Return true; wake up the procedure so we can act on proceed. return true; diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/MasterProcedureScheduler.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/MasterProcedureScheduler.java index 69a6e8fa271..373a9572588 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/MasterProcedureScheduler.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/MasterProcedureScheduler.java @@ -101,14 +101,18 @@ public class MasterProcedureScheduler extends AbstractProcedureScheduler { (n, k) -> n.compareKey((TableName) k); private final static AvlKeyComparator
* Locks on namespaces, tables, and regions.
@@ -49,6 +51,7 @@ class SchemaLocking {
// Single map for all regions irrespective of tables. Key is encoded region name.
private final Map