This is an automated email from the ASF dual-hosted git repository.

Apache9 pushed a commit to branch branch-2.5
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-2.5 by this push:
     new c0f998c063e HBASE-30346: Fix NPE in AbstractFSWAL WAL-roll debug 
logging aborting RegionServer (#8608)
c0f998c063e is described below

commit c0f998c063e8ed9b40be19a8b4f7a53b6f8f1e9f
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 8b558fdb214..a7f929c6fc0 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 bf9f4d89f18..634d7721b72 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;
@@ -56,6 +60,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;
@@ -380,4 +387,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");
+    }
+  }
 }

Reply via email to