From 9adf8a9826c56c356e3d76bb8c69fa24ee880801 Mon Sep 17 00:00:00 2001 From: Kodey Converse Date: Thu, 15 Jan 2026 06:37:37 -0500 Subject: [PATCH 1/3] Fix for NPE in region replication --- .../regionserver/RegionReplicaReplicationEndpoint.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/RegionReplicaReplicationEndpoint.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/RegionReplicaReplicationEndpoint.java index 7dbb4f008f85..898c24c975ea 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/RegionReplicaReplicationEndpoint.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/RegionReplicaReplicationEndpoint.java @@ -441,7 +441,8 @@ public void append(TableName tableName, byte[] encodedRegionName, byte[] row, // keep going to the cache, we will not learn of the replicas and their locations after // they come online. if (useCache && locations.size() == 1) { - if (tableDescriptors.get(tableName).getRegionReplication() > 1 && retries <= 3) { + TableDescriptor td = tableDescriptors.get(tableName); + if (td != null && td.getRegionReplication() > 1 && retries <= 3) { // Make an obnoxious log here. See how bad this issue is. Add a timer if happening // too much. LOG.info("Skipping location cache; only one location found for {}", tableName); From 72024e35620ace342d53cf7b5980bff47d747be8 Mon Sep 17 00:00:00 2001 From: Kodey Converse Date: Tue, 20 Jan 2026 12:11:54 -0500 Subject: [PATCH 2/3] Remove unneeded retries variable --- .../regionserver/RegionReplicaReplicationEndpoint.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/RegionReplicaReplicationEndpoint.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/RegionReplicaReplicationEndpoint.java index 898c24c975ea..bf8316626dd3 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/RegionReplicaReplicationEndpoint.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/RegionReplicaReplicationEndpoint.java @@ -427,7 +427,6 @@ public void append(TableName tableName, byte[] encodedRegionName, byte[] row, // invalidate the cache and check from meta RegionLocations locations = null; boolean useCache = true; - int retries = 0; while (true) { // get the replicas of the primary region try { @@ -442,12 +441,11 @@ public void append(TableName tableName, byte[] encodedRegionName, byte[] row, // they come online. if (useCache && locations.size() == 1) { TableDescriptor td = tableDescriptors.get(tableName); - if (td != null && td.getRegionReplication() > 1 && retries <= 3) { + if (td != null && td.getRegionReplication() > 1) { // Make an obnoxious log here. See how bad this issue is. Add a timer if happening // too much. LOG.info("Skipping location cache; only one location found for {}", tableName); useCache = false; - retries++; continue; } } From 1f4306770d2faee75759c3c9fc4a38bb528dbba6 Mon Sep 17 00:00:00 2001 From: Kodey Converse Date: Tue, 20 Jan 2026 12:12:30 -0500 Subject: [PATCH 3/3] Add a unit test for NPE --- .../TestRegionReplicaReplicationEndpoint.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/replication/regionserver/TestRegionReplicaReplicationEndpoint.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/replication/regionserver/TestRegionReplicaReplicationEndpoint.java index ffca0caabef6..861414c3709a 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/replication/regionserver/TestRegionReplicaReplicationEndpoint.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/replication/regionserver/TestRegionReplicaReplicationEndpoint.java @@ -22,6 +22,10 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -42,7 +46,9 @@ import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HRegionLocation; import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.RegionLocations; import org.apache.hadoop.hbase.ReplicationPeerNotFoundException; +import org.apache.hadoop.hbase.TableDescriptors; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.Waiter; import org.apache.hadoop.hbase.client.Admin; @@ -557,6 +563,59 @@ public void testMetaCacheMissTriggersRefresh() throws Exception { } } + @Test + public void testNullTableDescriptorDoesNotCauseNPE() throws Exception { + TableName tableName = TableName.valueOf(name.getMethodName()); + int regionReplication = 2; + HTableDescriptor htd = HTU.createTableDescriptor(tableName); + htd.setRegionReplication(regionReplication); + createOrEnableTableWithRetries(htd, true); + + Connection connection = ConnectionFactory.createConnection(HTU.getConfiguration()); + Table table = connection.getTable(tableName); + + try { + HTU.loadNumericRows(table, HBaseTestingUtility.fam1, 0, 100); + + RegionLocator rl = connection.getRegionLocator(tableName); + HRegionLocation hrl = rl.getRegionLocation(HConstants.EMPTY_BYTE_ARRAY); + byte[] encodedRegionName = hrl.getRegionInfo().getEncodedNameAsBytes(); + rl.close(); + + AtomicLong skippedEdits = new AtomicLong(); + RegionReplicaReplicationEndpoint.RegionReplicaOutputSink sink = + mock(RegionReplicaReplicationEndpoint.RegionReplicaOutputSink.class); + when(sink.getSkippedEditsCounter()).thenReturn(skippedEdits); + + TableDescriptors mockTableDescriptors = mock(TableDescriptors.class); + when(mockTableDescriptors.get(tableName)).thenReturn(null); + + RegionLocations singleLocation = new RegionLocations(hrl); + ClusterConnection mockConnection = mock(ClusterConnection.class); + when(mockConnection.locateRegion(eq(tableName), any(byte[].class), anyBoolean(), anyBoolean(), + anyInt())).thenReturn(singleLocation); + when(mockConnection.getConfiguration()).thenReturn(HTU.getConfiguration()); + + RegionReplicaReplicationEndpoint.RegionReplicaSinkWriter sinkWriter = + new RegionReplicaReplicationEndpoint.RegionReplicaSinkWriter(sink, mockConnection, + Executors.newSingleThreadExecutor(), Integer.MAX_VALUE, mockTableDescriptors); + + Cell cell = CellBuilderFactory.create(CellBuilderType.DEEP_COPY) + .setRow(Bytes.toBytes("testRow")).setFamily(HBaseTestingUtility.fam1) + .setValue(Bytes.toBytes("testValue")).setType(Type.Put).build(); + + Entry entry = + new Entry(new WALKeyImpl(encodedRegionName, tableName, 1), new WALEdit().add(cell)); + + sinkWriter.append(tableName, encodedRegionName, Bytes.toBytes("testRow"), + Lists.newArrayList(entry)); + + } finally { + table.close(); + connection.close(); + } + } + @Test public void testMetaCacheSkippedForSingleReplicaTable() throws Exception { TableName tableName = TableName.valueOf(name.getMethodName());