This is an automated email from the ASF dual-hosted git repository.
Apache9 pushed a commit to branch branch-2.6
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/branch-2.6 by this push:
new 001e9b05b46 HBASE-30346: Fix NPE in AbstractFSWAL WAL-roll debug
logging aborting RegionServer (#8608)
001e9b05b46 is described below
commit 001e9b05b4618fa9132a93624a16a6a97851f257
Author: Aman Poonia <[email protected]>
AuthorDate: Thu Sep 3 17:13:01 2026 +0530
HBASE-30346: Fix NPE in AbstractFSWAL WAL-roll debug logging aborting
RegionServer (#8608)
FSHLog#getPipeline() delegated directly to Hadoop's
DFSOutputStream#getPipeline(), which can legitimately return null (streamer
closed, or no block pipeline currently established between blocks). The
debug
logging added in HBASE-28775 replaced the null-tolerant Arrays.toString(...)
with Arrays.stream(...), which throws NPE on null, turning a benign
transient
HDFS state into a RegionServer self-abort during WAL rolling -- even though
the roll itself had already succeeded.
Normalize FSHLog#getPipeline() to never return null (an empty array
instead),
matching the contract AsyncFSWAL#getPipeline() already honors.
Signed-off-by: Viraj Jasani <[email protected]>
Signed-off by: Duo Zhang <[email protected]>
Signed-off-by: Andrew Purtell <[email protected]>
(cherry picked from commit c8be46e04c4b7831b40899c912da2e79073f046d)
---
.../hadoop/hbase/regionserver/wal/FSHLog.java | 12 +++++--
.../hadoop/hbase/regionserver/wal/TestFSHLog.java | 40 ++++++++++++++++++++++
2 files changed, 50 insertions(+), 2 deletions(-)
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/FSHLog.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/FSHLog.java
index bf6a6fe1652..550bebd01c1 100644
---
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/FSHLog.java
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/FSHLog.java
@@ -1200,13 +1200,21 @@ public class FSHLog extends AbstractFSWAL<Writer> {
}
/**
- * This method gets the pipeline for the current WAL.
+ * This method gets the pipeline for the current WAL. Note that
DFSOutputStream#getPipeline() can
+ * legitimately return null (e.g. the underlying streamer is closed, or no
block pipeline is
+ * currently established, such as between blocks) -- see HDFS-826. We
normalize that to an empty
+ * array here so that callers (in particular debug-log formatting in
AbstractFSWAL) never have to
+ * null-check, matching the contract already honored by
AsyncFSWAL#getPipeline().
*/
@Override
DatanodeInfo[] getPipeline() {
if (this.hdfs_out != null) {
if (this.hdfs_out.getWrappedStream() instanceof DFSOutputStream) {
- return ((DFSOutputStream)
this.hdfs_out.getWrappedStream()).getPipeline();
+ DatanodeInfo[] pipeline =
+ ((DFSOutputStream) this.hdfs_out.getWrappedStream()).getPipeline();
+ if (pipeline != null) {
+ return pipeline;
+ }
}
}
return new DatanodeInfo[0];
diff --git
a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/wal/TestFSHLog.java
b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/wal/TestFSHLog.java
index 9ec9e837d27..6e48e36ac73 100644
---
a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/wal/TestFSHLog.java
+++
b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/wal/TestFSHLog.java
@@ -19,7 +19,10 @@ package org.apache.hadoop.hbase.regionserver.wal;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import java.io.IOException;
import java.lang.reflect.Field;
@@ -32,6 +35,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HConstants;
@@ -57,6 +61,9 @@ import org.apache.hadoop.hbase.wal.WAL;
import org.apache.hadoop.hbase.wal.WALEdit;
import org.apache.hadoop.hbase.wal.WALKey;
import org.apache.hadoop.hbase.wal.WALProvider;
+import org.apache.hadoop.hdfs.DFSOutputStream;
+import org.apache.hadoop.hdfs.client.HdfsDataOutputStream;
+import org.apache.hadoop.hdfs.protocol.DatanodeInfo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@@ -382,4 +389,37 @@ public class TestFSHLog extends AbstractTestFSWAL {
region.close();
}
}
+
+ /**
+ * Regression test for HBASE-30346: FSHLog#getPipeline() must never return
null, even when the
+ * underlying DFSOutputStream#getPipeline() legitimately returns null (e.g.
the DFS streamer is
+ * closed, or no block pipeline is currently established -- see HDFS-826 and
the
+ * DFSOutputStream#getPipeline() javadoc: "returns the list of targets, if
any"). Prior to this
+ * fix, AbstractFSWAL#rollWriterInternal's debug-log statement called
+ * Arrays.stream(getPipeline()), which threw a NullPointerException whenever
this happened during
+ * a WAL roll, aborting the RegionServer on what was otherwise a successful
roll.
+ */
+ @Test
+ public void testGetPipelineDoesNotReturnNullWhenUnderlyingStreamerHasNone()
throws Exception {
+ FS.mkdirs(new Path(CommonFSUtils.getRootDir(CONF), this.name));
+ try (FSHLog log = new FSHLog(FS, CommonFSUtils.getRootDir(CONF), this.name,
+ HConstants.HREGION_OLDLOGDIR_NAME, CONF, null, true, null, null)) {
+ log.init();
+
+ // Simulate the legitimate HDFS contract: the wrapped DFSOutputStream
currently has no
+ // established pipeline (e.g. streamer closed, or between blocks) and
returns null.
+ DFSOutputStream mockDfsOut = mock(DFSOutputStream.class);
+ when(mockDfsOut.getPipeline()).thenReturn(null);
+ FSDataOutputStream wrappedOut = new HdfsDataOutputStream(mockDfsOut,
null);
+
+ Field hdfsOutField = FSHLog.class.getDeclaredField("hdfs_out");
+ hdfsOutField.setAccessible(true);
+ hdfsOutField.set(log, wrappedOut);
+
+ DatanodeInfo[] pipeline = log.getPipeline();
+ assertNotNull(pipeline, "getPipeline() must never return null");
+ assertEquals(0, pipeline.length,
+ "Should normalize a null underlying pipeline to an empty array");
+ }
+ }
}