HDFS-8397. Refactor the error handling code in DataStreamer. Contributed by Tsz Wo Nicholas Sze.

(cherry picked from commit 8f37873342)
This commit is contained in:
Jing Zhao 2015-05-15 16:14:54 -07:00
parent ce64720516
commit d6aa65d037
2 changed files with 309 additions and 239 deletions

View File

@ -221,6 +221,9 @@ Release 2.8.0 - UNRELEASED
HDFS-6888. Allow selectively audit logging ops (Chen He via vinayakumarb) HDFS-6888. Allow selectively audit logging ops (Chen He via vinayakumarb)
HDFS-8397. Refactor the error handling code in DataStreamer.
(Tsz Wo Nicholas Sze via jing9)
OPTIMIZATIONS OPTIMIZATIONS
HDFS-8026. Trace FSOutputSummer#writeChecksumChunks rather than HDFS-8026. Trace FSOutputSummer#writeChecksumChunks rather than

View File

@ -38,7 +38,6 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -46,6 +45,7 @@ import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.fs.StorageType; import org.apache.hadoop.fs.StorageType;
import org.apache.hadoop.hdfs.client.HdfsClientConfigKeys; import org.apache.hadoop.hdfs.client.HdfsClientConfigKeys;
import org.apache.hadoop.hdfs.client.HdfsClientConfigKeys.BlockWrite;
import org.apache.hadoop.hdfs.client.impl.DfsClientConf; import org.apache.hadoop.hdfs.client.impl.DfsClientConf;
import org.apache.hadoop.hdfs.protocol.BlockStoragePolicy; import org.apache.hadoop.hdfs.protocol.BlockStoragePolicy;
import org.apache.hadoop.hdfs.protocol.DSQuotaExceededException; import org.apache.hadoop.hdfs.protocol.DSQuotaExceededException;
@ -208,6 +208,133 @@ class DataStreamer extends Daemon {
} }
} }
static class ErrorState {
private boolean error = false;
private int badNodeIndex = -1;
private int restartingNodeIndex = -1;
private long restartingNodeDeadline = 0;
private final long datanodeRestartTimeout;
ErrorState(long datanodeRestartTimeout) {
this.datanodeRestartTimeout = datanodeRestartTimeout;
}
synchronized void reset() {
error = false;
badNodeIndex = -1;
restartingNodeIndex = -1;
restartingNodeDeadline = 0;
}
synchronized boolean hasError() {
return error;
}
synchronized boolean hasDatanodeError() {
return error && isNodeMarked();
}
synchronized void setError(boolean err) {
this.error = err;
}
synchronized void setBadNodeIndex(int index) {
this.badNodeIndex = index;
}
synchronized int getBadNodeIndex() {
return badNodeIndex;
}
synchronized int getRestartingNodeIndex() {
return restartingNodeIndex;
}
synchronized void initRestartingNode(int i, String message) {
restartingNodeIndex = i;
restartingNodeDeadline = Time.monotonicNow() + datanodeRestartTimeout;
// If the data streamer has already set the primary node
// bad, clear it. It is likely that the write failed due to
// the DN shutdown. Even if it was a real failure, the pipeline
// recovery will take care of it.
badNodeIndex = -1;
LOG.info(message);
}
synchronized boolean isRestartingNode() {
return restartingNodeIndex >= 0;
}
synchronized boolean isNodeMarked() {
return badNodeIndex >= 0 || isRestartingNode();
}
/**
* This method is used when no explicit error report was received, but
* something failed. The first node is a suspect or unsure about the cause
* so that it is marked as failed.
*/
synchronized void markFirstNodeIfNotMarked() {
// There should be no existing error and no ongoing restart.
if (!isNodeMarked()) {
badNodeIndex = 0;
}
}
synchronized void adjustState4RestartingNode() {
// Just took care of a node error while waiting for a node restart
if (restartingNodeIndex >= 0) {
// If the error came from a node further away than the restarting
// node, the restart must have been complete.
if (badNodeIndex > restartingNodeIndex) {
restartingNodeIndex = -1;
} else if (badNodeIndex < restartingNodeIndex) {
// the node index has shifted.
restartingNodeIndex--;
} else {
throw new IllegalStateException("badNodeIndex = " + badNodeIndex
+ " = restartingNodeIndex = " + restartingNodeIndex);
}
}
if (!isRestartingNode()) {
error = false;
}
badNodeIndex = -1;
}
synchronized void checkRestartingNodeDeadline(DatanodeInfo[] nodes) {
if (restartingNodeIndex >= 0) {
if (!error) {
throw new IllegalStateException("error=false while checking" +
" restarting node deadline");
}
// check badNodeIndex
if (badNodeIndex == restartingNodeIndex) {
// ignore, if came from the restarting node
badNodeIndex = -1;
}
// not within the deadline
if (Time.monotonicNow() >= restartingNodeDeadline) {
// expired. declare the restarting node dead
restartingNodeDeadline = 0;
final int i = restartingNodeIndex;
restartingNodeIndex = -1;
LOG.warn("Datanode " + i + " did not restart within "
+ datanodeRestartTimeout + "ms: " + nodes[i]);
// Mark the restarting node as failed. If there is any other failed
// node during the last pipeline construction attempt, it will not be
// overwritten/dropped. In this case, the restarting node will get
// excluded in the following attempt, if it still does not come up.
if (badNodeIndex == -1) {
badNodeIndex = i;
}
}
}
}
}
private volatile boolean streamerClosed = false; private volatile boolean streamerClosed = false;
private ExtendedBlock block; // its length is number of bytes acked private ExtendedBlock block; // its length is number of bytes acked
private Token<BlockTokenIdentifier> accessToken; private Token<BlockTokenIdentifier> accessToken;
@ -217,11 +344,8 @@ class DataStreamer extends Daemon {
private volatile DatanodeInfo[] nodes = null; // list of targets for current block private volatile DatanodeInfo[] nodes = null; // list of targets for current block
private volatile StorageType[] storageTypes = null; private volatile StorageType[] storageTypes = null;
private volatile String[] storageIDs = null; private volatile String[] storageIDs = null;
volatile boolean hasError = false; private final ErrorState errorState;
volatile int errorIndex = -1;
// Restarting node index
AtomicInteger restartingNodeIndex = new AtomicInteger(-1);
private long restartDeadline = 0; // Deadline of DN restart
private BlockConstructionStage stage; // block construction stage private BlockConstructionStage stage; // block construction stage
private long bytesSent = 0; // number of bytes that've been sent private long bytesSent = 0; // number of bytes that've been sent
private final boolean isLazyPersistFile; private final boolean isLazyPersistFile;
@ -287,11 +411,13 @@ class DataStreamer extends Daemon {
this.cachingStrategy = cachingStrategy; this.cachingStrategy = cachingStrategy;
this.byteArrayManager = byteArrayManage; this.byteArrayManager = byteArrayManage;
this.isLazyPersistFile = isLazyPersist(stat); this.isLazyPersistFile = isLazyPersist(stat);
this.dfsclientSlowLogThresholdMs =
dfsClient.getConf().getSlowIoWarningThresholdMs();
this.excludedNodes = initExcludedNodes();
this.isAppend = isAppend; this.isAppend = isAppend;
this.favoredNodes = favoredNodes; this.favoredNodes = favoredNodes;
final DfsClientConf conf = dfsClient.getConf();
this.dfsclientSlowLogThresholdMs = conf.getSlowIoWarningThresholdMs();
this.excludedNodes = initExcludedNodes(conf.getExcludedNodesCacheExpiry());
this.errorState = new ErrorState(conf.getDatanodeRestartTimeout());
} }
/** /**
@ -334,7 +460,6 @@ class DataStreamer extends Daemon {
void setPipelineInConstruction(LocatedBlock lastBlock) throws IOException{ void setPipelineInConstruction(LocatedBlock lastBlock) throws IOException{
// setup pipeline to append to the last block XXX retries?? // setup pipeline to append to the last block XXX retries??
setPipeline(lastBlock); setPipeline(lastBlock);
errorIndex = -1; // no errors yet.
if (nodes.length < 1) { if (nodes.length < 1) {
throw new IOException("Unable to retrieve blocks locations " + throw new IOException("Unable to retrieve blocks locations " +
" for last block " + block + " for last block " + block +
@ -375,6 +500,10 @@ class DataStreamer extends Daemon {
stage = BlockConstructionStage.PIPELINE_SETUP_CREATE; stage = BlockConstructionStage.PIPELINE_SETUP_CREATE;
} }
private boolean shouldStop() {
return streamerClosed || errorState.hasError() || !dfsClient.clientRunning;
}
/* /*
* streamer thread is the only thread that opens streams to datanode, * streamer thread is the only thread that opens streams to datanode,
* and closes them. Any error recovery is also done by this thread. * and closes them. Any error recovery is also done by this thread.
@ -385,7 +514,7 @@ class DataStreamer extends Daemon {
TraceScope scope = NullScope.INSTANCE; TraceScope scope = NullScope.INSTANCE;
while (!streamerClosed && dfsClient.clientRunning) { while (!streamerClosed && dfsClient.clientRunning) {
// if the Responder encountered an error, shutdown Responder // if the Responder encountered an error, shutdown Responder
if (hasError && response != null) { if (errorState.hasError() && response != null) {
try { try {
response.close(); response.close();
response.join(); response.join();
@ -398,17 +527,13 @@ class DataStreamer extends Daemon {
DFSPacket one; DFSPacket one;
try { try {
// process datanode IO errors if any // process datanode IO errors if any
boolean doSleep = false; boolean doSleep = processDatanodeError();
if (hasError && (errorIndex >= 0 || restartingNodeIndex.get() >= 0)) {
doSleep = processDatanodeError();
}
final int halfSocketTimeout = dfsClient.getConf().getSocketTimeout()/2; final int halfSocketTimeout = dfsClient.getConf().getSocketTimeout()/2;
synchronized (dataQueue) { synchronized (dataQueue) {
// wait for a packet to be sent. // wait for a packet to be sent.
long now = Time.monotonicNow(); long now = Time.monotonicNow();
while ((!streamerClosed && !hasError && dfsClient.clientRunning while ((!shouldStop() && dataQueue.size() == 0 &&
&& dataQueue.size() == 0 &&
(stage != BlockConstructionStage.DATA_STREAMING || (stage != BlockConstructionStage.DATA_STREAMING ||
stage == BlockConstructionStage.DATA_STREAMING && stage == BlockConstructionStage.DATA_STREAMING &&
now - lastPacket < halfSocketTimeout)) || doSleep ) { now - lastPacket < halfSocketTimeout)) || doSleep ) {
@ -424,13 +549,12 @@ class DataStreamer extends Daemon {
doSleep = false; doSleep = false;
now = Time.monotonicNow(); now = Time.monotonicNow();
} }
if (streamerClosed || hasError || !dfsClient.clientRunning) { if (shouldStop()) {
continue; continue;
} }
// get packet to be sent. // get packet to be sent.
if (dataQueue.isEmpty()) { if (dataQueue.isEmpty()) {
one = createHeartbeatPacket(); one = createHeartbeatPacket();
assert one != null;
} else { } else {
try { try {
backOffIfNecessary(); backOffIfNecessary();
@ -460,7 +584,7 @@ class DataStreamer extends Daemon {
LOG.debug("Append to block " + block); LOG.debug("Append to block " + block);
} }
setupPipelineForAppendOrRecovery(); setupPipelineForAppendOrRecovery();
if (true == streamerClosed) { if (streamerClosed) {
continue; continue;
} }
initDataStreaming(); initDataStreaming();
@ -478,8 +602,7 @@ class DataStreamer extends Daemon {
if (one.isLastPacketInBlock()) { if (one.isLastPacketInBlock()) {
// wait for all data packets have been successfully acked // wait for all data packets have been successfully acked
synchronized (dataQueue) { synchronized (dataQueue) {
while (!streamerClosed && !hasError && while (!shouldStop() && ackQueue.size() != 0) {
ackQueue.size() != 0 && dfsClient.clientRunning) {
try { try {
// wait for acks to arrive from datanodes // wait for acks to arrive from datanodes
dataQueue.wait(1000); dataQueue.wait(1000);
@ -488,7 +611,7 @@ class DataStreamer extends Daemon {
} }
} }
} }
if (streamerClosed || hasError || !dfsClient.clientRunning) { if (shouldStop()) {
continue; continue;
} }
stage = BlockConstructionStage.PIPELINE_CLOSE; stage = BlockConstructionStage.PIPELINE_CLOSE;
@ -524,7 +647,7 @@ class DataStreamer extends Daemon {
// effect. Pipeline recovery can handle only one node error at a // effect. Pipeline recovery can handle only one node error at a
// time. If the primary node fails again during the recovery, it // time. If the primary node fails again during the recovery, it
// will be taken out then. // will be taken out then.
tryMarkPrimaryDatanodeFailed(); errorState.markFirstNodeIfNotMarked();
throw e; throw e;
} finally { } finally {
writeScope.close(); writeScope.close();
@ -537,7 +660,7 @@ class DataStreamer extends Daemon {
bytesSent = tmpBytesSent; bytesSent = tmpBytesSent;
} }
if (streamerClosed || hasError || !dfsClient.clientRunning) { if (shouldStop()) {
continue; continue;
} }
@ -545,12 +668,11 @@ class DataStreamer extends Daemon {
if (one.isLastPacketInBlock()) { if (one.isLastPacketInBlock()) {
// wait for the close packet has been acked // wait for the close packet has been acked
synchronized (dataQueue) { synchronized (dataQueue) {
while (!streamerClosed && !hasError && while (!shouldStop() && ackQueue.size() != 0) {
ackQueue.size() != 0 && dfsClient.clientRunning) {
dataQueue.wait(1000);// wait for acks to arrive from datanodes dataQueue.wait(1000);// wait for acks to arrive from datanodes
} }
} }
if (streamerClosed || hasError || !dfsClient.clientRunning) { if (shouldStop()) {
continue; continue;
} }
@ -564,7 +686,7 @@ class DataStreamer extends Daemon {
} }
} catch (Throwable e) { } catch (Throwable e) {
// Log warning if there was a real error. // Log warning if there was a real error.
if (restartingNodeIndex.get() == -1) { if (!errorState.isRestartingNode()) {
// Since their messages are descriptive enough, do not always // Since their messages are descriptive enough, do not always
// log a verbose stack-trace WARN for quota exceptions. // log a verbose stack-trace WARN for quota exceptions.
if (e instanceof QuotaExceededException) { if (e instanceof QuotaExceededException) {
@ -575,8 +697,8 @@ class DataStreamer extends Daemon {
} }
lastException.set(e); lastException.set(e);
assert !(e instanceof NullPointerException); assert !(e instanceof NullPointerException);
hasError = true; errorState.setError(true);
if (errorIndex == -1 && restartingNodeIndex.get() == -1) { if (!errorState.isNodeMarked()) {
// Not a datanode issue // Not a datanode issue
streamerClosed = true; streamerClosed = true;
} }
@ -773,40 +895,6 @@ class DataStreamer extends Daemon {
} }
} }
// The following synchronized methods are used whenever
// errorIndex or restartingNodeIndex is set. This is because
// check & set needs to be atomic. Simply reading variables
// does not require a synchronization. When responder is
// not running (e.g. during pipeline recovery), there is no
// need to use these methods.
/** Set the error node index. Called by responder */
synchronized void setErrorIndex(int idx) {
errorIndex = idx;
}
/** Set the restarting node index. Called by responder */
synchronized void setRestartingNodeIndex(int idx) {
restartingNodeIndex.set(idx);
// If the data streamer has already set the primary node
// bad, clear it. It is likely that the write failed due to
// the DN shutdown. Even if it was a real failure, the pipeline
// recovery will take care of it.
errorIndex = -1;
}
/**
* This method is used when no explicit error report was received,
* but something failed. When the primary node is a suspect or
* unsure about the cause, the primary node is marked as failed.
*/
synchronized void tryMarkPrimaryDatanodeFailed() {
// There should be no existing error and no ongoing restart.
if ((errorIndex == -1) && (restartingNodeIndex.get() == -1)) {
errorIndex = 0;
}
}
/** /**
* Examine whether it is worth waiting for a node to restart. * Examine whether it is worth waiting for a node to restart.
* @param index the node index * @param index the node index
@ -883,20 +971,16 @@ class DataStreamer extends Daemon {
// the local node or the only one in the pipeline. // the local node or the only one in the pipeline.
if (PipelineAck.isRestartOOBStatus(reply) && if (PipelineAck.isRestartOOBStatus(reply) &&
shouldWaitForRestart(i)) { shouldWaitForRestart(i)) {
restartDeadline = dfsClient.getConf().getDatanodeRestartTimeout() final String message = "Datanode " + i + " is restarting: "
+ Time.monotonicNow(); + targets[i];
setRestartingNodeIndex(i); errorState.initRestartingNode(i, message);
String message = "A datanode is restarting: " + targets[i];
LOG.info(message);
throw new IOException(message); throw new IOException(message);
} }
// node error // node error
if (reply != SUCCESS) { if (reply != SUCCESS) {
setErrorIndex(i); // first bad datanode errorState.setBadNodeIndex(i); // mark bad datanode
throw new IOException("Bad response " + reply + throw new IOException("Bad response " + reply +
" for block " + block + " for " + block + " from datanode " + targets[i]);
" from datanode " +
targets[i]);
} }
} }
@ -954,14 +1038,12 @@ class DataStreamer extends Daemon {
} catch (Exception e) { } catch (Exception e) {
if (!responderClosed) { if (!responderClosed) {
lastException.set(e); lastException.set(e);
hasError = true; errorState.setError(true);
// If no explicit error report was received, mark the primary errorState.markFirstNodeIfNotMarked();
// node as failed.
tryMarkPrimaryDatanodeFailed();
synchronized (dataQueue) { synchronized (dataQueue) {
dataQueue.notifyAll(); dataQueue.notifyAll();
} }
if (restartingNodeIndex.get() == -1) { if (!errorState.isRestartingNode()) {
LOG.warn("Exception for " + block, e); LOG.warn("Exception for " + block, e);
} }
responderClosed = true; responderClosed = true;
@ -978,11 +1060,16 @@ class DataStreamer extends Daemon {
} }
} }
// If this stream has encountered any errors so far, shutdown /**
// threads and mark stream as closed. Returns true if we should * If this stream has encountered any errors, shutdown threads
// sleep for a while after returning from this call. * and mark the stream as closed.
// *
* @return true if it should sleep for a while after returning.
*/
private boolean processDatanodeError() throws IOException { private boolean processDatanodeError() throws IOException {
if (!errorState.hasDatanodeError()) {
return false;
}
if (response != null) { if (response != null) {
LOG.info("Error Recovery for " + block + LOG.info("Error Recovery for " + block +
" waiting for responder to exit. "); " waiting for responder to exit. ");
@ -1064,7 +1151,7 @@ class DataStreamer extends Daemon {
.append("The current failed datanode replacement policy is ") .append("The current failed datanode replacement policy is ")
.append(dfsClient.dtpReplaceDatanodeOnFailure).append(", and ") .append(dfsClient.dtpReplaceDatanodeOnFailure).append(", and ")
.append("a client may configure this via '") .append("a client may configure this via '")
.append(HdfsClientConfigKeys.BlockWrite.ReplaceDatanodeOnFailure.POLICY_KEY) .append(BlockWrite.ReplaceDatanodeOnFailure.POLICY_KEY)
.append("' in its configuration.") .append("' in its configuration.")
.toString()); .toString());
} }
@ -1190,156 +1277,140 @@ class DataStreamer extends Daemon {
boolean success = false; boolean success = false;
long newGS = 0L; long newGS = 0L;
while (!success && !streamerClosed && dfsClient.clientRunning) { while (!success && !streamerClosed && dfsClient.clientRunning) {
// Sleep before reconnect if a dn is restarting. if (!handleRestartingDatanode()) {
// This process will be repeated until the deadline or the datanode return false;
// starts back up.
if (restartingNodeIndex.get() >= 0) {
// 4 seconds or the configured deadline period, whichever is shorter.
// This is the retry interval and recovery will be retried in this
// interval until timeout or success.
long delay = Math.min(dfsClient.getConf().getDatanodeRestartTimeout(),
4000L);
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {
lastException.set(new IOException("Interrupted while waiting for " +
"datanode to restart. " + nodes[restartingNodeIndex.get()]));
streamerClosed = true;
return false;
}
}
boolean isRecovery = hasError;
// remove bad datanode from list of datanodes.
// If errorIndex was not set (i.e. appends), then do not remove
// any datanodes
//
if (errorIndex >= 0) {
StringBuilder pipelineMsg = new StringBuilder();
for (int j = 0; j < nodes.length; j++) {
pipelineMsg.append(nodes[j]);
if (j < nodes.length - 1) {
pipelineMsg.append(", ");
}
}
if (nodes.length <= 1) {
lastException.set(new IOException("All datanodes " + pipelineMsg
+ " are bad. Aborting..."));
streamerClosed = true;
return false;
}
LOG.warn("Error Recovery for block " + block +
" in pipeline " + pipelineMsg +
": bad datanode " + nodes[errorIndex]);
failed.add(nodes[errorIndex]);
DatanodeInfo[] newnodes = new DatanodeInfo[nodes.length-1];
arraycopy(nodes, newnodes, errorIndex);
final StorageType[] newStorageTypes = new StorageType[newnodes.length];
arraycopy(storageTypes, newStorageTypes, errorIndex);
final String[] newStorageIDs = new String[newnodes.length];
arraycopy(storageIDs, newStorageIDs, errorIndex);
setPipeline(newnodes, newStorageTypes, newStorageIDs);
// Just took care of a node error while waiting for a node restart
if (restartingNodeIndex.get() >= 0) {
// If the error came from a node further away than the restarting
// node, the restart must have been complete.
if (errorIndex > restartingNodeIndex.get()) {
restartingNodeIndex.set(-1);
} else if (errorIndex < restartingNodeIndex.get()) {
// the node index has shifted.
restartingNodeIndex.decrementAndGet();
} else {
// this shouldn't happen...
assert false;
}
}
if (restartingNodeIndex.get() == -1) {
hasError = false;
}
lastException.clear();
errorIndex = -1;
} }
// Check if replace-datanode policy is satisfied. final boolean isRecovery = errorState.hasError();
if (dfsClient.dtpReplaceDatanodeOnFailure.satisfy(stat.getReplication(), if (!handleBadDatanode()) {
nodes, isAppend, isHflushed)) { return false;
try {
addDatanode2ExistingPipeline();
} catch(IOException ioe) {
if (!dfsClient.dtpReplaceDatanodeOnFailure.isBestEffort()) {
throw ioe;
}
LOG.warn("Failed to replace datanode."
+ " Continue with the remaining datanodes since "
+ HdfsClientConfigKeys.BlockWrite.ReplaceDatanodeOnFailure.BEST_EFFORT_KEY
+ " is set to true.", ioe);
}
} }
handleDatanodeReplacement();
// get a new generation stamp and an access token // get a new generation stamp and an access token
LocatedBlock lb = dfsClient.namenode.updateBlockForPipeline(block, dfsClient.clientName); final LocatedBlock lb = updateBlockForPipeline();
newGS = lb.getBlock().getGenerationStamp(); newGS = lb.getBlock().getGenerationStamp();
accessToken = lb.getBlockToken(); accessToken = lb.getBlockToken();
// set up the pipeline again with the remaining nodes // set up the pipeline again with the remaining nodes
if (failPacket) { // for testing success = createBlockOutputStream(nodes, storageTypes, newGS, isRecovery);
success = createBlockOutputStream(nodes, storageTypes, newGS, isRecovery);
failPacket = false;
try {
// Give DNs time to send in bad reports. In real situations,
// good reports should follow bad ones, if client committed
// with those nodes.
Thread.sleep(2000);
} catch (InterruptedException ie) {}
} else {
success = createBlockOutputStream(nodes, storageTypes, newGS, isRecovery);
}
if (restartingNodeIndex.get() >= 0) { failPacket4Testing();
assert hasError == true;
// check errorIndex set above errorState.checkRestartingNodeDeadline(nodes);
if (errorIndex == restartingNodeIndex.get()) {
// ignore, if came from the restarting node
errorIndex = -1;
}
// still within the deadline
if (Time.monotonicNow() < restartDeadline) {
continue; // with in the deadline
}
// expired. declare the restarting node dead
restartDeadline = 0;
int expiredNodeIndex = restartingNodeIndex.get();
restartingNodeIndex.set(-1);
LOG.warn("Datanode did not restart in time: " +
nodes[expiredNodeIndex]);
// Mark the restarting node as failed. If there is any other failed
// node during the last pipeline construction attempt, it will not be
// overwritten/dropped. In this case, the restarting node will get
// excluded in the following attempt, if it still does not come up.
if (errorIndex == -1) {
errorIndex = expiredNodeIndex;
}
// From this point on, normal pipeline recovery applies.
}
} // while } // while
if (success) { if (success) {
// update pipeline at the namenode block = updatePipeline(newGS);
ExtendedBlock newBlock = new ExtendedBlock(
block.getBlockPoolId(), block.getBlockId(), block.getNumBytes(), newGS);
dfsClient.namenode.updatePipeline(dfsClient.clientName, block, newBlock,
nodes, storageIDs);
// update client side generation stamp
block = newBlock;
} }
return false; // do not sleep, continue processing return false; // do not sleep, continue processing
} }
/**
* Sleep if a node is restarting.
* This process is repeated until the deadline or the node starts back up.
* @return true if it should continue.
*/
private boolean handleRestartingDatanode() {
if (errorState.isRestartingNode()) {
// 4 seconds or the configured deadline period, whichever is shorter.
// This is the retry interval and recovery will be retried in this
// interval until timeout or success.
final long delay = Math.min(errorState.datanodeRestartTimeout, 4000L);
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {
lastException.set(new IOException(
"Interrupted while waiting for restarting "
+ nodes[errorState.getRestartingNodeIndex()]));
streamerClosed = true;
return false;
}
}
return true;
}
/**
* Remove bad node from list of nodes if badNodeIndex was set.
* @return true if it should continue.
*/
private boolean handleBadDatanode() {
final int badNodeIndex = errorState.getBadNodeIndex();
if (badNodeIndex >= 0) {
if (nodes.length <= 1) {
lastException.set(new IOException("All datanodes "
+ Arrays.toString(nodes) + " are bad. Aborting..."));
streamerClosed = true;
return false;
}
LOG.warn("Error Recovery for " + block + " in pipeline "
+ Arrays.toString(nodes) + ": datanode " + badNodeIndex
+ "("+ nodes[badNodeIndex] + ") is bad.");
failed.add(nodes[badNodeIndex]);
DatanodeInfo[] newnodes = new DatanodeInfo[nodes.length-1];
arraycopy(nodes, newnodes, badNodeIndex);
final StorageType[] newStorageTypes = new StorageType[newnodes.length];
arraycopy(storageTypes, newStorageTypes, badNodeIndex);
final String[] newStorageIDs = new String[newnodes.length];
arraycopy(storageIDs, newStorageIDs, badNodeIndex);
setPipeline(newnodes, newStorageTypes, newStorageIDs);
errorState.adjustState4RestartingNode();
lastException.clear();
}
return true;
}
/** Add a datanode if replace-datanode policy is satisfied. */
private void handleDatanodeReplacement() throws IOException {
if (dfsClient.dtpReplaceDatanodeOnFailure.satisfy(stat.getReplication(),
nodes, isAppend, isHflushed)) {
try {
addDatanode2ExistingPipeline();
} catch(IOException ioe) {
if (!dfsClient.dtpReplaceDatanodeOnFailure.isBestEffort()) {
throw ioe;
}
LOG.warn("Failed to replace datanode."
+ " Continue with the remaining datanodes since "
+ BlockWrite.ReplaceDatanodeOnFailure.BEST_EFFORT_KEY
+ " is set to true.", ioe);
}
}
}
private void failPacket4Testing() {
if (failPacket) { // for testing
failPacket = false;
try {
// Give DNs time to send in bad reports. In real situations,
// good reports should follow bad ones, if client committed
// with those nodes.
Thread.sleep(2000);
} catch (InterruptedException ie) {}
}
}
LocatedBlock updateBlockForPipeline() throws IOException {
return dfsClient.namenode.updateBlockForPipeline(
block, dfsClient.clientName);
}
/** update pipeline at the namenode */
ExtendedBlock updatePipeline(long newGS) throws IOException {
final ExtendedBlock newBlock = new ExtendedBlock(
block.getBlockPoolId(), block.getBlockId(), block.getNumBytes(), newGS);
dfsClient.namenode.updatePipeline(dfsClient.clientName, block, newBlock,
nodes, storageIDs);
return newBlock;
}
/** /**
* Open a DataStreamer to a DataNode so that it can be written to. * Open a DataStreamer to a DataNode so that it can be written to.
* This happens when a file is created and each time a new block is allocated. * This happens when a file is created and each time a new block is allocated.
@ -1354,9 +1425,8 @@ class DataStreamer extends Daemon {
boolean success = false; boolean success = false;
ExtendedBlock oldBlock = block; ExtendedBlock oldBlock = block;
do { do {
hasError = false; errorState.reset();
lastException.clear(); lastException.clear();
errorIndex = -1;
success = false; success = false;
DatanodeInfo[] excluded = DatanodeInfo[] excluded =
@ -1382,8 +1452,9 @@ class DataStreamer extends Daemon {
dfsClient.namenode.abandonBlock(block, stat.getFileId(), src, dfsClient.namenode.abandonBlock(block, stat.getFileId(), src,
dfsClient.clientName); dfsClient.clientName);
block = null; block = null;
LOG.info("Excluding datanode " + nodes[errorIndex]); final DatanodeInfo badNode = nodes[errorState.getBadNodeIndex()];
excludedNodes.put(nodes[errorIndex], nodes[errorIndex]); LOG.info("Excluding datanode " + badNode);
excludedNodes.put(badNode, badNode);
} }
} while (!success && --count >= 0); } while (!success && --count >= 0);
@ -1464,7 +1535,7 @@ class DataStreamer extends Daemon {
// from the local datanode. Thus it is safe to treat this as a // from the local datanode. Thus it is safe to treat this as a
// regular node error. // regular node error.
if (PipelineAck.isRestartOOBStatus(pipelineStatus) && if (PipelineAck.isRestartOOBStatus(pipelineStatus) &&
restartingNodeIndex.get() == -1) { !errorState.isRestartingNode()) {
checkRestart = true; checkRestart = true;
throw new IOException("A datanode is restarting."); throw new IOException("A datanode is restarting.");
} }
@ -1475,10 +1546,9 @@ class DataStreamer extends Daemon {
assert null == blockStream : "Previous blockStream unclosed"; assert null == blockStream : "Previous blockStream unclosed";
blockStream = out; blockStream = out;
result = true; // success result = true; // success
restartingNodeIndex.set(-1); errorState.reset();
hasError = false;
} catch (IOException ie) { } catch (IOException ie) {
if (restartingNodeIndex.get() == -1) { if (!errorState.isRestartingNode()) {
LOG.info("Exception in createBlockOutputStream", ie); LOG.info("Exception in createBlockOutputStream", ie);
} }
if (ie instanceof InvalidEncryptionKeyException && refetchEncryptionKey > 0) { if (ie instanceof InvalidEncryptionKeyException && refetchEncryptionKey > 0) {
@ -1498,24 +1568,21 @@ class DataStreamer extends Daemon {
for (int i = 0; i < nodes.length; i++) { for (int i = 0; i < nodes.length; i++) {
// NB: Unconditionally using the xfer addr w/o hostname // NB: Unconditionally using the xfer addr w/o hostname
if (firstBadLink.equals(nodes[i].getXferAddr())) { if (firstBadLink.equals(nodes[i].getXferAddr())) {
errorIndex = i; errorState.setBadNodeIndex(i);
break; break;
} }
} }
} else { } else {
assert checkRestart == false; assert checkRestart == false;
errorIndex = 0; errorState.setBadNodeIndex(0);
} }
final int i = errorState.getBadNodeIndex();
// Check whether there is a restart worth waiting for. // Check whether there is a restart worth waiting for.
if (checkRestart && shouldWaitForRestart(errorIndex)) { if (checkRestart && shouldWaitForRestart(i)) {
restartDeadline = dfsClient.getConf().getDatanodeRestartTimeout() errorState.initRestartingNode(i, "Datanode " + i + " is restarting: " + nodes[i]);
+ Time.monotonicNow();
restartingNodeIndex.set(errorIndex);
errorIndex = -1;
LOG.info("Waiting for the datanode to be restarted: " +
nodes[restartingNodeIndex.get()]);
} }
hasError = true; errorState.setError(true);
lastException.set(ie); lastException.set(ie);
result = false; // error result = false; // error
} finally { } finally {
@ -1699,10 +1766,10 @@ class DataStreamer extends Daemon {
return new DFSPacket(buf, 0, 0, DFSPacket.HEART_BEAT_SEQNO, 0, false); return new DFSPacket(buf, 0, 0, DFSPacket.HEART_BEAT_SEQNO, 0, false);
} }
private LoadingCache<DatanodeInfo, DatanodeInfo> initExcludedNodes() { private static LoadingCache<DatanodeInfo, DatanodeInfo> initExcludedNodes(
return CacheBuilder.newBuilder().expireAfterWrite( long excludedNodesCacheExpiry) {
dfsClient.getConf().getExcludedNodesCacheExpiry(), return CacheBuilder.newBuilder()
TimeUnit.MILLISECONDS) .expireAfterWrite(excludedNodesCacheExpiry, TimeUnit.MILLISECONDS)
.removalListener(new RemovalListener<DatanodeInfo, DatanodeInfo>() { .removalListener(new RemovalListener<DatanodeInfo, DatanodeInfo>() {
@Override @Override
public void onRemoval( public void onRemoval(