[
https://issues.apache.org/jira/browse/HDFS-17916?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Charles Connell updated HDFS-17916:
-----------------------------------
Description:
A [certain code
path|https://github.com/apache/hadoop/blob/b322c3ce2c10b45cec2f9acbe6f00fb75c054caa/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DataStreamer.java#L1422]
in the DFS client DataStreamer appears to discard DFSPacket objects without
returning their contained byte arrays to the ByteArrayManager. I discovered
this bug at my company after we had HBase server threads hung for hours at
{{{}ByteArrayManager#allocate(){}}}. I took a heap dump of a high-uptime but
relatively healthy HBase server, and found evidence it of leaked byte arrays
there too. In the heap dump, the two FixedLengthManagers both had
{{{}numAllocated = 9{}}}, but there were zero live {{DFSPacket}} objects. This
suggests that the byte arrays, and their containing {{DFSPackets}} had been
garbage collected, unbeknownst to {{{}FixedLengthManager{}}}.
In DataStreamer.java starting at line 1410, the {{DFSPacket}} that is
{{{}remove(){}}}'d from {{dataQueue}} is allowed to be garbage collected
without further interaction.
{code:java}
if (!streamerClosed && dfsClient.clientRunning) {
if (stage == BlockConstructionStage.PIPELINE_CLOSE) {
// If we had an error while closing the pipeline, we go through a
fast-path
// where the BlockReceiver does not run. Instead, the DataNode just
finalizes
// the block immediately during the 'connect ack' process. So, we want to
pull
// the end-of-block packet from the dataQueue, since we don't actually
have
// a true pipeline to send it over.
synchronized (dataQueue) {
DFSPacket endOfBlockPacket = dataQueue.remove(); // <-- leak: buf
never released
Span span = endOfBlockPacket.getSpan();
if (span != null) {
span.finish();
endOfBlockPacket.setSpan(null);
}
assert endOfBlockPacket.isLastPacketInBlock();
assert lastAckedSeqno == endOfBlockPacket.getSeqno() - 1;
lastAckedSeqno = endOfBlockPacket.getSeqno();
pipelineRecoveryCount = 0;
dataQueue.notifyAll();
}
endBlock();
} else {
initDataStreaming();
}
}
{code}
This could be fixed by inserting this line somewhere above:
{code:java}
endOfBlockPacket.releaseBuffer(byteArrayManager);
{code}
Claude Opus 4.7 was used to assist in finding this bug. Its findings were
verified myself and I stand by them.
was:
A [certain code
path|https://github.com/apache/hadoop/blob/b322c3ce2c10b45cec2f9acbe6f00fb75c054caa/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DataStreamer.java#L1422]
in the DFS client DataStreamer appears to discard DFSPacket objects without
returning their contained byte arrays to the ByteArrayManager. I discovered
this bug at my company after we had HBase server threads hung for hours at
{{{}ByteArrayManager#allocate(){}}}. I took a heap dump of a high-uptime but
relatively healthy HBase server, and found evidence it of leaked byte arrays
there too. In the heap dump, the two FixedLengthManagers both had
{{{}numAllocated = 9{}}}, but there were zero live {{DFSPacket}} objects. This
suggests that the byte arrays, and their containing {{DFSPackets}} had been
garbage collected, unbeknownst to {{{}FixedLengthManager{}}}.
In DataStreamer.java starting at line 1410, the {{DFSPacket}} that is
{{{}remove(){}}}'d from {{dataQueue}} is allowed to be garbage collected
without further interaction.
{code:java}
if (!streamerClosed && dfsClient.clientRunning) {
if (stage == BlockConstructionStage.PIPELINE_CLOSE) {
// If we had an error while closing the pipeline, we go through a
fast-path
// where the BlockReceiver does not run. Instead, the DataNode just
finalizes
// the block immediately during the 'connect ack' process. So, we want to
pull
// the end-of-block packet from the dataQueue, since we don't actually
have
// a true pipeline to send it over.
synchronized (dataQueue) {
DFSPacket endOfBlockPacket = dataQueue.remove(); // <-- leak: buf
never released
Span span = endOfBlockPacket.getSpan();
if (span != null) {
span.finish();
endOfBlockPacket.setSpan(null);
}
assert endOfBlockPacket.isLastPacketInBlock();
assert lastAckedSeqno == endOfBlockPacket.getSeqno() - 1;
lastAckedSeqno = endOfBlockPacket.getSeqno();
pipelineRecoveryCount = 0;
dataQueue.notifyAll();
}
endBlock();
} else {
initDataStreaming();
}
}
{code}
This could be fixed by inserting this line somewhere above:
{code:java}
endOfBlockPacket.releaseBuffer(byteArrayManager);
{code}
Claude Opus 4.7 was used to assist in finding this bug.
> DataStreamer#processDatanodeOrExternalError() fails to return byte arrays to
> ByteArrayManager
> ---------------------------------------------------------------------------------------------
>
> Key: HDFS-17916
> URL: https://issues.apache.org/jira/browse/HDFS-17916
> Project: Hadoop HDFS
> Issue Type: Bug
> Components: hdfs-client
> Affects Versions: 3.3.6, 3.5.0, 3.4.3
> Reporter: Charles Connell
> Priority: Major
>
> A [certain code
> path|https://github.com/apache/hadoop/blob/b322c3ce2c10b45cec2f9acbe6f00fb75c054caa/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DataStreamer.java#L1422]
> in the DFS client DataStreamer appears to discard DFSPacket objects without
> returning their contained byte arrays to the ByteArrayManager. I discovered
> this bug at my company after we had HBase server threads hung for hours at
> {{{}ByteArrayManager#allocate(){}}}. I took a heap dump of a high-uptime but
> relatively healthy HBase server, and found evidence it of leaked byte arrays
> there too. In the heap dump, the two FixedLengthManagers both had
> {{{}numAllocated = 9{}}}, but there were zero live {{DFSPacket}} objects.
> This suggests that the byte arrays, and their containing {{DFSPackets}} had
> been garbage collected, unbeknownst to {{{}FixedLengthManager{}}}.
> In DataStreamer.java starting at line 1410, the {{DFSPacket}} that is
> {{{}remove(){}}}'d from {{dataQueue}} is allowed to be garbage collected
> without further interaction.
> {code:java}
> if (!streamerClosed && dfsClient.clientRunning) {
> if (stage == BlockConstructionStage.PIPELINE_CLOSE) {
> // If we had an error while closing the pipeline, we go through a
> fast-path
> // where the BlockReceiver does not run. Instead, the DataNode just
> finalizes
> // the block immediately during the 'connect ack' process. So, we want
> to pull
> // the end-of-block packet from the dataQueue, since we don't actually
> have
> // a true pipeline to send it over.
> synchronized (dataQueue) {
> DFSPacket endOfBlockPacket = dataQueue.remove(); // <-- leak: buf
> never released
> Span span = endOfBlockPacket.getSpan();
> if (span != null) {
> span.finish();
> endOfBlockPacket.setSpan(null);
> }
> assert endOfBlockPacket.isLastPacketInBlock();
> assert lastAckedSeqno == endOfBlockPacket.getSeqno() - 1;
> lastAckedSeqno = endOfBlockPacket.getSeqno();
> pipelineRecoveryCount = 0;
> dataQueue.notifyAll();
> }
> endBlock();
> } else {
> initDataStreaming();
> }
> }
> {code}
> This could be fixed by inserting this line somewhere above:
> {code:java}
> endOfBlockPacket.releaseBuffer(byteArrayManager);
> {code}
> Claude Opus 4.7 was used to assist in finding this bug. Its findings were
> verified myself and I stand by them.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]