blob: d04dbef66ba199da09f8b050bf1ee4a9f6057ea3 [file] [log] [blame]
/**
* 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.hadoop.hbase.master.assignment;
import java.util.Arrays;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.DoNotRetryRegionException;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.RegionOfflineException;
import org.apache.hadoop.hbase.exceptions.UnexpectedStateException;
import org.apache.hadoop.hbase.master.RegionState;
import org.apache.hadoop.hbase.master.RegionState.State;
import org.apache.hadoop.hbase.procedure2.ProcedureEvent;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Current Region State. Most fields are synchronized with meta region, i.e, we will update meta
* immediately after we modify this RegionStateNode, and usually under the lock. The only exception
* is {@link #lastHost}, which should not be used for critical condition.
* <p/>
* Typically, the only way to modify this class is through {@link TransitRegionStateProcedure}, and
* we will record the TRSP along with this RegionStateNode to make sure that there could at most one
* TRSP. For other operations, such as SCP, we will first get the lock, and then try to schedule a
* TRSP. If there is already one, then the solution will be different:
* <ul>
* <li>For SCP, we will update the region state in meta to tell the TRSP to retry.</li>
* <li>For DisableTableProcedure, as we have the xlock, we can make sure that the TRSP has not been
* executed yet, so just unset it and attach a new one. The original one will quit immediately when
* executing.</li>
* <li>For split/merge, we will fail immediately as there is no actual operations yet so no
* harm.</li>
* <li>For EnableTableProcedure/TruncateTableProcedure, we can make sure that there will be no TRSP
* attached with the RSNs.</li>
* <li>For other procedures, you'd better use ReopenTableRegionsProcedure. The RTRP will take care
* of lots of corner cases when reopening regions.</li>
* </ul>
* <p/>
* Several fields are declared with {@code volatile}, which means you are free to get it without
* lock, but usually you should not use these fields without locking for critical condition, as it
* will be easily to introduce inconsistency. For example, you are free to dump the status and show
* it on web without locking, but if you want to change the state of the RegionStateNode by checking
* the current state, you'd better have the lock...
*/
@InterfaceAudience.Private
public class RegionStateNode implements Comparable<RegionStateNode> {
private static final Logger LOG = LoggerFactory.getLogger(RegionStateNode.class);
private static final class AssignmentProcedureEvent extends ProcedureEvent<RegionInfo> {
public AssignmentProcedureEvent(final RegionInfo regionInfo) {
super(regionInfo);
}
}
final Lock lock = new ReentrantLock();
private final RegionInfo regionInfo;
private final ProcedureEvent<?> event;
private final ConcurrentMap<RegionInfo, RegionStateNode> ritMap;
// volatile only for getLastUpdate and test usage, the upper layer should sync on the
// RegionStateNode before accessing usually.
private volatile TransitRegionStateProcedure procedure = null;
private volatile ServerName regionLocation = null;
// notice that, the lastHost will only be updated when a region is successfully CLOSED through
// UnassignProcedure, so do not use it for critical condition as the data maybe stale and unsync
// with the data in meta.
private volatile ServerName lastHost = null;
/**
* A Region-in-Transition (RIT) moves through states. See {@link State} for complete list. A
* Region that is opened moves from OFFLINE => OPENING => OPENED.
*/
private volatile State state = State.OFFLINE;
/**
* Updated whenever a call to {@link #setRegionLocation(ServerName)} or
* {@link #setState(RegionState.State, RegionState.State...)}.
*/
private volatile long lastUpdate = 0;
private volatile long openSeqNum = HConstants.NO_SEQNUM;
RegionStateNode(RegionInfo regionInfo, ConcurrentMap<RegionInfo, RegionStateNode> ritMap) {
this.regionInfo = regionInfo;
this.event = new AssignmentProcedureEvent(regionInfo);
this.ritMap = ritMap;
}
/**
* @param update new region state this node should be assigned.
* @param expected current state should be in this given list of expected states
* @return true, if current state is in expected list; otherwise false.
*/
public boolean setState(final State update, final State... expected) {
if (!isInState(expected)) {
return false;
}
this.state = update;
this.lastUpdate = EnvironmentEdgeManager.currentTime();
return true;
}
/**
* Put region into OFFLINE mode (set state and clear location).
* @return Last recorded server deploy
*/
public ServerName offline() {
setState(State.OFFLINE);
return setRegionLocation(null);
}
/**
* Set new {@link State} but only if currently in <code>expected</code> State (if not, throw
* {@link UnexpectedStateException}.
*/
public void transitionState(final State update, final State... expected)
throws UnexpectedStateException {
if (!setState(update, expected)) {
throw new UnexpectedStateException("Expected " + Arrays.toString(expected) +
" so could move to " + update + " but current state=" + getState());
}
}
/**
* Notice that, we will return true if {@code expected} is empty.
* <p/>
* This is a bit strange but we need this logic, for example, we can change the state to OPENING
* from any state, as in SCP we will not change the state to CLOSED before opening the region.
*/
public boolean isInState(State... expected) {
if (expected.length == 0) {
return true;
}
return getState().matches(expected);
}
public boolean isStuck() {
return isInState(State.FAILED_OPEN) && getProcedure() != null;
}
public boolean isInTransition() {
return getProcedure() != null;
}
public long getLastUpdate() {
TransitRegionStateProcedure proc = this.procedure;
if (proc != null) {
long lastUpdate = proc.getLastUpdate();
return lastUpdate != 0 ? lastUpdate : proc.getSubmittedTime();
}
return lastUpdate;
}
public void setLastHost(final ServerName serverName) {
this.lastHost = serverName;
}
public void setOpenSeqNum(final long seqId) {
this.openSeqNum = seqId;
}
public ServerName setRegionLocation(final ServerName serverName) {
ServerName lastRegionLocation = this.regionLocation;
if (LOG.isTraceEnabled() && serverName == null) {
LOG.trace("Tracking when we are set to null " + this, new Throwable("TRACE"));
}
this.regionLocation = serverName;
this.lastUpdate = EnvironmentEdgeManager.currentTime();
return lastRegionLocation;
}
public TransitRegionStateProcedure setProcedure(TransitRegionStateProcedure proc) {
assert this.procedure == null;
this.procedure = proc;
ritMap.put(regionInfo, this);
return proc;
}
public void unsetProcedure(TransitRegionStateProcedure proc) {
assert this.procedure == proc;
this.procedure = null;
ritMap.remove(regionInfo, this);
}
public TransitRegionStateProcedure getProcedure() {
return procedure;
}
public ProcedureEvent<?> getProcedureEvent() {
return event;
}
public RegionInfo getRegionInfo() {
return regionInfo;
}
public TableName getTable() {
return getRegionInfo().getTable();
}
public boolean isSystemTable() {
return getTable().isSystemTable();
}
public ServerName getLastHost() {
return lastHost;
}
public ServerName getRegionLocation() {
return regionLocation;
}
public State getState() {
return state;
}
public long getOpenSeqNum() {
return openSeqNum;
}
public int getFormatVersion() {
// we don't have any format for now
// it should probably be in regionInfo.getFormatVersion()
return 0;
}
public RegionState toRegionState() {
return new RegionState(getRegionInfo(), getState(), getLastUpdate(), getRegionLocation());
}
@Override
public int compareTo(final RegionStateNode other) {
// NOTE: RegionInfo sort by table first, so we are relying on that.
// we have a TestRegionState#testOrderedByTable() that check for that.
return RegionInfo.COMPARATOR.compare(getRegionInfo(), other.getRegionInfo());
}
@Override
public int hashCode() {
return getRegionInfo().hashCode();
}
@Override
public boolean equals(final Object other) {
if (this == other) {
return true;
}
if (!(other instanceof RegionStateNode)) {
return false;
}
return compareTo((RegionStateNode) other) == 0;
}
@Override
public String toString() {
return toDescriptiveString();
}
public String toShortString() {
// rit= is the current Region-In-Transition State -- see State enum.
return String.format("state=%s, location=%s", getState(), getRegionLocation());
}
public String toDescriptiveString() {
return String.format("%s, table=%s, region=%s", toShortString(), getTable(),
getRegionInfo().getEncodedName());
}
public void checkOnline() throws DoNotRetryRegionException {
RegionInfo ri = getRegionInfo();
State s = state;
if (s != State.OPEN) {
throw new DoNotRetryRegionException(ri.getEncodedName() + " is not OPEN; state=" + s);
}
if (ri.isSplitParent()) {
throw new DoNotRetryRegionException(
ri.getEncodedName() + " is not online (splitParent=true)");
}
if (ri.isSplit()) {
throw new DoNotRetryRegionException(ri.getEncodedName() + " has split=true");
}
if (ri.isOffline()) {
// RegionOfflineException is not instance of DNRIOE so wrap it.
throw new DoNotRetryRegionException(new RegionOfflineException(ri.getEncodedName()));
}
}
public void lock() {
lock.lock();
}
public void unlock() {
lock.unlock();
}
}