Skip to content

Commit 6367bc1

Browse files
committed
HDFS-11608. HDFS write crashed with block size greater than 2 GB. Contributed by Xiaobing Zhou.
(cherry picked from commit 0eacd4c) (cherry picked from commit 0391c92)
1 parent b1dfdea commit 6367bc1

File tree

3 files changed

+165
-4
lines changed

3 files changed

+165
-4
lines changed

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

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.apache.hadoop.hdfs.protocol.SnapshotAccessControlException;
5050
import org.apache.hadoop.hdfs.protocol.UnresolvedPathException;
5151
import org.apache.hadoop.hdfs.protocol.datatransfer.PacketHeader;
52+
import org.apache.hadoop.hdfs.protocol.datatransfer.PacketReceiver;
5253
import org.apache.hadoop.hdfs.security.token.block.BlockTokenIdentifier;
5354
import org.apache.hadoop.hdfs.server.datanode.CachingStrategy;
5455
import org.apache.hadoop.hdfs.server.namenode.RetryStartFileException;
@@ -120,6 +121,7 @@ public class DFSOutputStream extends FSOutputSummer
120121
private final EnumSet<AddBlockFlag> addBlockFlags;
121122
protected final AtomicReference<CachingStrategy> cachingStrategy;
122123
private FileEncryptionInfo fileEncryptionInfo;
124+
private int writePacketSize;
123125

124126
/** Use {@link ByteArrayManager} to create buffer for non-heartbeat packets.*/
125127
protected DFSPacket createPacket(int packetSize, int chunksPerPkt,
@@ -199,7 +201,9 @@ private DFSOutputStream(DFSClient dfsClient, String src,
199201
DFSClient.LOG.debug("Set non-null progress callback on DFSOutputStream "
200202
+"{}", src);
201203
}
202-
204+
205+
initWritePacketSize();
206+
203207
this.bytesPerChecksum = checksum.getBytesPerChecksum();
204208
if (bytesPerChecksum <= 0) {
205209
throw new HadoopIllegalArgumentException(
@@ -214,6 +218,21 @@ private DFSOutputStream(DFSClient dfsClient, String src,
214218
this.byteArrayManager = dfsClient.getClientContext().getByteArrayManager();
215219
}
216220

221+
/**
222+
* Ensures the configured writePacketSize never exceeds
223+
* PacketReceiver.MAX_PACKET_SIZE.
224+
*/
225+
private void initWritePacketSize() {
226+
writePacketSize = dfsClient.getConf().getWritePacketSize();
227+
if (writePacketSize > PacketReceiver.MAX_PACKET_SIZE) {
228+
LOG.warn(
229+
"Configured write packet exceeds {} bytes as max,"
230+
+ " using {} bytes.",
231+
PacketReceiver.MAX_PACKET_SIZE, PacketReceiver.MAX_PACKET_SIZE);
232+
writePacketSize = PacketReceiver.MAX_PACKET_SIZE;
233+
}
234+
}
235+
217236
/** Construct a new output stream for creating a file. */
218237
protected DFSOutputStream(DFSClient dfsClient, String src, HdfsFileStatus stat,
219238
EnumSet<CreateFlag> flag, Progressable progress,
@@ -450,12 +469,28 @@ protected void adjustChunkBoundary() {
450469
}
451470

452471
if (!getStreamer().getAppendChunk()) {
453-
int psize = Math.min((int)(blockSize- getStreamer().getBytesCurBlock()),
454-
dfsClient.getConf().getWritePacketSize());
472+
final int psize = (int) Math
473+
.min(blockSize - getStreamer().getBytesCurBlock(), writePacketSize);
455474
computePacketChunkSize(psize, bytesPerChecksum);
456475
}
457476
}
458477

478+
/**
479+
* Used in test only.
480+
*/
481+
@VisibleForTesting
482+
void setAppendChunk(final boolean appendChunk) {
483+
getStreamer().setAppendChunk(appendChunk);
484+
}
485+
486+
/**
487+
* Used in test only.
488+
*/
489+
@VisibleForTesting
490+
void setBytesCurBlock(final long bytesCurBlock) {
491+
getStreamer().setBytesCurBlock(bytesCurBlock);
492+
}
493+
459494
/**
460495
* if encountering a block boundary, send an empty packet to
461496
* indicate the end of block and reset bytesCurBlock.

hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/protocol/datatransfer/PacketReceiver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public class PacketReceiver implements Closeable {
4545
* The max size of any single packet. This prevents OOMEs when
4646
* invalid data is sent.
4747
*/
48-
private static final int MAX_PACKET_SIZE = 16 * 1024 * 1024;
48+
public static final int MAX_PACKET_SIZE = 16 * 1024 * 1024;
4949

5050
static final Logger LOG = LoggerFactory.getLogger(PacketReceiver.class);
5151

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

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
package org.apache.hadoop.hdfs;
1919

2020
import java.io.DataOutputStream;
21+
import java.io.File;
2122
import java.io.IOException;
2223
import java.lang.reflect.Field;
24+
import java.lang.reflect.InvocationTargetException;
2325
import java.lang.reflect.Method;
2426
import java.util.ArrayList;
2527
import java.util.EnumSet;
@@ -41,10 +43,13 @@
4143
import org.apache.hadoop.hdfs.protocol.HdfsConstants;
4244
import org.apache.hadoop.hdfs.protocol.HdfsFileStatus;
4345
import org.apache.hadoop.hdfs.protocol.datatransfer.BlockConstructionStage;
46+
import org.apache.hadoop.hdfs.protocol.datatransfer.PacketReceiver;
4447
import org.apache.hadoop.hdfs.server.blockmanagement.BlockManager;
4548
import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor;
4649
import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeManager;
4750
import org.apache.hadoop.hdfs.server.protocol.DatanodeStorage;
51+
import org.apache.hadoop.test.GenericTestUtils;
52+
import org.apache.hadoop.test.PathUtils;
4853
import org.apache.htrace.core.SpanId;
4954
import org.junit.AfterClass;
5055
import org.junit.Assert;
@@ -64,6 +69,9 @@
6469
import static org.mockito.Mockito.spy;
6570
import static org.mockito.Mockito.when;
6671

72+
import static org.apache.hadoop.hdfs.client.HdfsClientConfigKeys.DFS_CLIENT_WRITE_PACKET_SIZE_DEFAULT;
73+
import static org.apache.hadoop.hdfs.client.HdfsClientConfigKeys.DFS_CLIENT_WRITE_PACKET_SIZE_KEY;
74+
6775
public class TestDFSOutputStream {
6876
static MiniDFSCluster cluster;
6977

@@ -133,6 +141,124 @@ public void testComputePacketChunkSize() throws Exception {
133141
Assert.assertTrue((Integer) field.get(dos) + 257 < packetSize);
134142
}
135143

144+
/**
145+
* This tests preventing overflows of package size and bodySize.
146+
* <p>
147+
* See also https://issues.apache.org/jira/browse/HDFS-11608.
148+
* </p>
149+
* @throws IOException
150+
* @throws SecurityException
151+
* @throws NoSuchFieldException
152+
* @throws InvocationTargetException
153+
* @throws IllegalArgumentException
154+
* @throws IllegalAccessException
155+
* @throws NoSuchMethodException
156+
*/
157+
@Test(timeout=60000)
158+
public void testPreventOverflow() throws IOException, NoSuchFieldException,
159+
SecurityException, IllegalAccessException, IllegalArgumentException,
160+
InvocationTargetException, NoSuchMethodException {
161+
162+
final int defaultWritePacketSize = DFS_CLIENT_WRITE_PACKET_SIZE_DEFAULT;
163+
int configuredWritePacketSize = defaultWritePacketSize;
164+
int finalWritePacketSize = defaultWritePacketSize;
165+
166+
/* test default WritePacketSize, e.g. 64*1024 */
167+
runAdjustChunkBoundary(configuredWritePacketSize, finalWritePacketSize);
168+
169+
/* test large WritePacketSize, e.g. 1G */
170+
configuredWritePacketSize = 1000 * 1024 * 1024;
171+
finalWritePacketSize = PacketReceiver.MAX_PACKET_SIZE;
172+
runAdjustChunkBoundary(configuredWritePacketSize, finalWritePacketSize);
173+
}
174+
175+
/**
176+
* @configuredWritePacketSize the configured WritePacketSize.
177+
* @finalWritePacketSize the final WritePacketSize picked by
178+
* {@link DFSOutputStream#adjustChunkBoundary}
179+
*/
180+
private void runAdjustChunkBoundary(
181+
final int configuredWritePacketSize,
182+
final int finalWritePacketSize) throws IOException, NoSuchFieldException,
183+
SecurityException, IllegalAccessException, IllegalArgumentException,
184+
InvocationTargetException, NoSuchMethodException {
185+
186+
final boolean appendChunk = false;
187+
final long blockSize = 3221225500L;
188+
final long bytesCurBlock = 1073741824L;
189+
final int bytesPerChecksum = 512;
190+
final int checksumSize = 4;
191+
final int chunkSize = bytesPerChecksum + checksumSize;
192+
final int packateMaxHeaderLength = 33;
193+
194+
MiniDFSCluster dfsCluster = null;
195+
final File baseDir = new File(PathUtils.getTestDir(getClass()),
196+
GenericTestUtils.getMethodName());
197+
198+
try {
199+
final Configuration dfsConf = new Configuration();
200+
dfsConf.set(MiniDFSCluster.HDFS_MINIDFS_BASEDIR,
201+
baseDir.getAbsolutePath());
202+
dfsConf.setInt(DFS_CLIENT_WRITE_PACKET_SIZE_KEY,
203+
configuredWritePacketSize);
204+
dfsCluster = new MiniDFSCluster.Builder(dfsConf).numDataNodes(1).build();
205+
dfsCluster.waitActive();
206+
207+
final FSDataOutputStream os = dfsCluster.getFileSystem()
208+
.create(new Path(baseDir.getAbsolutePath(), "testPreventOverflow"));
209+
final DFSOutputStream dos = (DFSOutputStream) Whitebox
210+
.getInternalState(os, "wrappedStream");
211+
212+
/* set appendChunk */
213+
final Method setAppendChunkMethod = dos.getClass()
214+
.getDeclaredMethod("setAppendChunk", boolean.class);
215+
setAppendChunkMethod.setAccessible(true);
216+
setAppendChunkMethod.invoke(dos, appendChunk);
217+
218+
/* set bytesCurBlock */
219+
final Method setBytesCurBlockMethod = dos.getClass()
220+
.getDeclaredMethod("setBytesCurBlock", long.class);
221+
setBytesCurBlockMethod.setAccessible(true);
222+
setBytesCurBlockMethod.invoke(dos, bytesCurBlock);
223+
224+
/* set blockSize */
225+
final Field blockSizeField = dos.getClass().getDeclaredField("blockSize");
226+
blockSizeField.setAccessible(true);
227+
blockSizeField.setLong(dos, blockSize);
228+
229+
/* call adjustChunkBoundary */
230+
final Method method = dos.getClass()
231+
.getDeclaredMethod("adjustChunkBoundary");
232+
method.setAccessible(true);
233+
method.invoke(dos);
234+
235+
/* get and verify writePacketSize */
236+
final Field writePacketSizeField = dos.getClass()
237+
.getDeclaredField("writePacketSize");
238+
writePacketSizeField.setAccessible(true);
239+
Assert.assertEquals(writePacketSizeField.getInt(dos),
240+
finalWritePacketSize);
241+
242+
/* get and verify chunksPerPacket */
243+
final Field chunksPerPacketField = dos.getClass()
244+
.getDeclaredField("chunksPerPacket");
245+
chunksPerPacketField.setAccessible(true);
246+
Assert.assertEquals(chunksPerPacketField.getInt(dos),
247+
(finalWritePacketSize - packateMaxHeaderLength) / chunkSize);
248+
249+
/* get and verify packetSize */
250+
final Field packetSizeField = dos.getClass()
251+
.getDeclaredField("packetSize");
252+
packetSizeField.setAccessible(true);
253+
Assert.assertEquals(packetSizeField.getInt(dos),
254+
chunksPerPacketField.getInt(dos) * chunkSize);
255+
} finally {
256+
if (dfsCluster != null) {
257+
dfsCluster.shutdown();
258+
}
259+
}
260+
}
261+
136262
@Test
137263
public void testCongestionBackoff() throws IOException {
138264
DfsClientConf dfsClientConf = mock(DfsClientConf.class);

0 commit comments

Comments
 (0)