Skip to content

Commit 7bebad6

Browse files
sodonneljojochuang
authored andcommitted
HDFS-14706. Checksums are not checked if block meta file is less than 7 bytes. Contributed by Stephen O'Donnell.
Signed-off-by: Wei-Chiu Chuang <[email protected]>
1 parent 4f5f46e commit 7bebad6

File tree

3 files changed

+110
-2
lines changed

3 files changed

+110
-2
lines changed

hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSOutputStream.java

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.nio.ByteBuffer;
2424
import java.nio.channels.ClosedChannelException;
2525
import java.util.EnumSet;
26+
import java.util.Random;
2627
import java.util.concurrent.atomic.AtomicReference;
2728

2829
import org.apache.hadoop.HadoopIllegalArgumentException;
@@ -126,6 +127,8 @@ public class DFSOutputStream extends FSOutputSummer
126127
protected final AtomicReference<CachingStrategy> cachingStrategy;
127128
private FileEncryptionInfo fileEncryptionInfo;
128129
private int writePacketSize;
130+
private boolean leaseRecovered = false;
131+
private boolean exceptionInClose = false; //for unit test
129132

130133
/** Use {@link ByteArrayManager} to create buffer for non-heartbeat packets.*/
131134
protected DFSPacket createPacket(int packetSize, int chunksPerPkt,
@@ -836,6 +839,39 @@ protected void closeThreads(boolean force) throws IOException {
836839
}
837840
}
838841

842+
@VisibleForTesting
843+
public void setExceptionInClose(boolean enable) {
844+
exceptionInClose = enable;
845+
}
846+
847+
private class EmulateExceptionInClose {
848+
private Random rand = null;
849+
private int kickedNum;
850+
851+
EmulateExceptionInClose(int callNum) {
852+
if (exceptionInClose) {
853+
rand = new Random();
854+
}
855+
kickedNum = callNum;
856+
}
857+
858+
void kickRandomException() throws IOException {
859+
if (exceptionInClose) {
860+
if (kickedNum > 0) {
861+
if (rand.nextInt(kickedNum) == 1) {
862+
throw new IOException("Emulated random IOException in close");
863+
}
864+
}
865+
}
866+
}
867+
868+
void kickException() throws IOException {
869+
if (exceptionInClose) {
870+
throw new IOException("Emulated IOException in close");
871+
}
872+
}
873+
}
874+
839875
/**
840876
* Closes this output stream and releases any system
841877
* resources associated with this stream.
@@ -858,7 +894,20 @@ public void close() throws IOException {
858894
}
859895

860896
protected synchronized void closeImpl() throws IOException {
897+
boolean recoverOnCloseException = dfsClient.getConfiguration().getBoolean(
898+
HdfsClientConfigKeys.Write.RECOVER_ON_CLOSE_EXCEPTION_KEY,
899+
HdfsClientConfigKeys.Write.RECOVER_ON_CLOSE_EXCEPTION_DEFAULT);
861900
if (isClosed()) {
901+
if (recoverOnCloseException && !leaseRecovered) {
902+
try {
903+
dfsClient.endFileLease(fileId);
904+
dfsClient.recoverLease(src);
905+
leaseRecovered = true;
906+
} catch (Exception e) {
907+
LOG.warn("Fail to recover lease for {}", src, e);
908+
}
909+
}
910+
862911
LOG.debug("Closing an already closed stream. [Stream:{}, streamer:{}]",
863912
closed, getStreamer().streamerClosed());
864913
try {
@@ -875,8 +924,11 @@ protected synchronized void closeImpl() throws IOException {
875924
return;
876925
}
877926

927+
EmulateExceptionInClose eei = new EmulateExceptionInClose(5);
878928
try {
879-
flushBuffer(); // flush from all upper layers
929+
flushBuffer(); // flush from all upper layers
930+
// for test
931+
eei.kickRandomException();
880932

881933
if (currentPacket != null) {
882934
enqueueCurrentPacket();
@@ -887,12 +939,28 @@ protected synchronized void closeImpl() throws IOException {
887939
}
888940

889941
try {
890-
flushInternal(); // flush all data to Datanodes
942+
flushInternal(); // flush all data to Datanodes
891943
} catch (IOException ioe) {
892944
cleanupAndRethrowIOException(ioe);
893945
}
946+
// for test
947+
eei.kickRandomException();
894948
completeFile();
949+
// for test
950+
eei.kickException();
895951
} catch (ClosedChannelException ignored) {
952+
} catch (IOException ioe) {
953+
if (recoverOnCloseException) {
954+
try {
955+
dfsClient.endFileLease(fileId);
956+
dfsClient.recoverLease(src);
957+
leaseRecovered = true;
958+
} catch (Exception e) {
959+
// Ignore exception rendered by recoverLease. Throw original
960+
// exception
961+
}
962+
}
963+
throw ioe;
896964
} finally {
897965
// Failures may happen when flushing data.
898966
// Streamers may keep waiting for the new block information.

hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/client/HdfsClientConfigKeys.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,9 @@ interface Write {
307307
String EXCLUDE_NODES_CACHE_EXPIRY_INTERVAL_KEY =
308308
PREFIX + "exclude.nodes.cache.expiry.interval.millis";
309309
long EXCLUDE_NODES_CACHE_EXPIRY_INTERVAL_DEFAULT = 10*MINUTE;
310+
String RECOVER_ON_CLOSE_EXCEPTION_KEY =
311+
PREFIX + "recover.on.close.exception";
312+
boolean RECOVER_ON_CLOSE_EXCEPTION_DEFAULT = false;
310313

311314
interface ByteArrayManager {
312315
String PREFIX = Write.PREFIX + "byte-array-manager.";

hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSOutputStream.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.Map;
3232
import java.util.Random;
3333

34+
import org.apache.commons.lang3.reflect.FieldUtils;
3435
import org.apache.hadoop.conf.Configuration;
3536
import org.apache.hadoop.fs.CreateFlag;
3637
import org.apache.hadoop.fs.FSDataOutputStream;
@@ -40,6 +41,7 @@
4041
import org.apache.hadoop.fs.StreamCapabilities.StreamCapability;
4142
import org.apache.hadoop.fs.permission.FsPermission;
4243
import org.apache.hadoop.hdfs.DataStreamer.LastExceptionInStreamer;
44+
import org.apache.hadoop.hdfs.client.HdfsClientConfigKeys;
4345
import org.apache.hadoop.hdfs.client.impl.DfsClientConf;
4446
import org.apache.hadoop.hdfs.protocol.BlockListAsLongs;
4547
import org.apache.hadoop.hdfs.protocol.DatanodeInfo;
@@ -371,6 +373,41 @@ public void testStreamFlush() throws Exception {
371373
os.close();
372374
}
373375

376+
/**
377+
* If dfs.client.recover-on-close-exception.enable is set and exception
378+
* happens in close, the local lease should be closed and lease in namenode
379+
* should be recovered.
380+
*/
381+
@Test
382+
public void testExceptionInClose() throws Exception {
383+
String testStr = "Test exception in close";
384+
DistributedFileSystem fs = cluster.getFileSystem();
385+
Path testFile = new Path("/closeexception");
386+
fs.getConf().setBoolean(
387+
HdfsClientConfigKeys.Write.RECOVER_ON_CLOSE_EXCEPTION_KEY, true);
388+
FSDataOutputStream os = fs.create(testFile);
389+
DFSOutputStream dos =
390+
(DFSOutputStream) FieldUtils.readField(os, "wrappedStream", true);
391+
dos.setExceptionInClose(true);
392+
os.write(testStr.getBytes());
393+
try {
394+
dos.close();
395+
// There should be exception
396+
Assert.assertTrue(false);
397+
} catch (IOException ioe) {
398+
GenericTestUtils.waitFor(() -> {
399+
boolean closed;
400+
try {
401+
closed = fs.isFileClosed(testFile);
402+
} catch (IOException e) {
403+
return false;
404+
}
405+
return closed;
406+
}, 1000, 5000);
407+
Assert.assertTrue(fs.isFileClosed(testFile));
408+
}
409+
}
410+
374411
@AfterClass
375412
public static void tearDown() {
376413
if (cluster != null) {

0 commit comments

Comments
 (0)