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

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new cfb1775bf9 Add test and avoid infinite loop, fixes #6154 (#6155)
cfb1775bf9 is described below

commit cfb1775bf93b74e476c67359153f2a8f8820b6dd
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Tue Dec 9 15:49:18 2025 +0100

    Add test and avoid infinite loop, fixes #6154 (#6155)
---
 .../apache/hop/core/hash/ByteArrayHashIndex.java   |   4 +-
 .../hop/core/hash/ByteArrayHashIndexTest.java      | 106 ++++++++++++++++++++-
 2 files changed, 107 insertions(+), 3 deletions(-)

diff --git 
a/core/src/main/java/org/apache/hop/core/hash/ByteArrayHashIndex.java 
b/core/src/main/java/org/apache/hop/core/hash/ByteArrayHashIndex.java
index a8811043f0..1415ac633f 100644
--- a/core/src/main/java/org/apache/hop/core/hash/ByteArrayHashIndex.java
+++ b/core/src/main/java/org/apache/hop/core/hash/ByteArrayHashIndex.java
@@ -103,6 +103,7 @@ public class ByteArrayHashIndex {
     ByteArrayHashIndexEntry check = index[indexPointer];
     if (check == null) {
       index[indexPointer] = new ByteArrayHashIndexEntry(hashCode, key, value, 
index[indexPointer]);
+      resize();
       return;
     }
 
@@ -190,7 +191,7 @@ public class ByteArrayHashIndex {
           if (check == null) {
             // Yes, plenty of room
             //
-            newIndex[newIndexPointer] = check;
+            newIndex[newIndexPointer] = entry;
           } else {
             // No, we need to look for a nice spot to put the hash entry...
             //
@@ -212,7 +213,6 @@ public class ByteArrayHashIndex {
             previousCheck.nextEntry = entry;
             newIndex[newIndexPointer] = entry;
           }
-          newIndex[newIndexPointer] = entry;
         }
       }
 
diff --git 
a/core/src/test/java/org/apache/hop/core/hash/ByteArrayHashIndexTest.java 
b/core/src/test/java/org/apache/hop/core/hash/ByteArrayHashIndexTest.java
index 90d65ad353..6dd4c3cdf7 100644
--- a/core/src/test/java/org/apache/hop/core/hash/ByteArrayHashIndexTest.java
+++ b/core/src/test/java/org/apache/hop/core/hash/ByteArrayHashIndexTest.java
@@ -21,16 +21,28 @@ import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.hop.core.exception.HopValueException;
 import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaInteger;
 import org.junit.Test;
 
 public class ByteArrayHashIndexTest {
 
   @Test
   public void testArraySizeConstructor() {
-    ByteArrayHashIndex obj = new ByteArrayHashIndex(new RowMeta(), 1);
+    // Test default constructor (uses STANDARD_INDEX_SIZE = 512)
+    ByteArrayHashIndex obj = new ByteArrayHashIndex(new RowMeta());
+    assertEquals(512, obj.getSize());
+    assertEquals(0, obj.getCount());
+
+    // Test with explicit sizes - should round up to power of 2
+    obj = new ByteArrayHashIndex(new RowMeta(), 1);
     assertEquals(1, obj.getSize());
 
     obj = new ByteArrayHashIndex(new RowMeta(), 2);
@@ -58,4 +70,96 @@ public class ByteArrayHashIndexTest {
     assertNotNull(obj.get(new byte[] {10}));
     assertArrayEquals(new byte[] {53, 12}, obj.get(new byte[] {10}));
   }
+
+  /**
+   * Regression test: verify that count is incremented when inserting into an 
empty home slot. This
+   * ensures resize() is called and prevents infinite loops when the table 
fills up.
+   */
+  @Test
+  public void testCountIncrementedOnEmptySlotInsert() throws HopValueException 
{
+    RowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaInteger("id"));
+
+    ByteArrayHashIndex hashIndex = new ByteArrayHashIndex(rowMeta, 4);
+
+    assertEquals("Initial count should be 0", 0, hashIndex.getCount());
+
+    // Insert first entry - goes into empty home slot
+    byte[] key1 = RowMeta.extractData(rowMeta, new Object[] {1L});
+    hashIndex.put(key1, new byte[] {1});
+    assertEquals("Count should be 1 after first insert", 1, 
hashIndex.getCount());
+
+    // Insert second entry
+    byte[] key2 = RowMeta.extractData(rowMeta, new Object[] {2L});
+    hashIndex.put(key2, new byte[] {2});
+    assertEquals("Count should be 2 after second insert", 2, 
hashIndex.getCount());
+
+    // Insert more entries to trigger resize
+    for (int i = 3; i <= 10; i++) {
+      byte[] key = RowMeta.extractData(rowMeta, new Object[] {(long) i});
+      hashIndex.put(key, new byte[] {(byte) i});
+    }
+    assertEquals("Count should be 10 after all inserts", 10, 
hashIndex.getCount());
+
+    // Verify table was resized (started at 4, should be larger now)
+    assertTrue(hashIndex.getSize() > 4);
+
+    // Verify all entries are retrievable
+    for (int i = 1; i <= 10; i++) {
+      byte[] key = RowMeta.extractData(rowMeta, new Object[] {(long) i});
+      byte[] value = hashIndex.get(key);
+      assertNotNull("Entry " + i + " should be retrievable", value);
+      assertEquals("Entry " + i + " should have correct value", (byte) i, 
value[0]);
+    }
+  }
+
+  /**
+   * Tests that put() does not hang when filling up the table. Before the fix, 
inserting into an
+   * empty home slot didn't call resize(), causing infinite loops when the 
table became full.
+   */
+  @Test
+  public void testPutDoesNotHangWhenTableFills() throws Exception {
+    final int TIMEOUT_SECONDS = 2;
+    final CountDownLatch done = new CountDownLatch(1);
+    final AtomicReference<Throwable> error = new AtomicReference<>();
+
+    Thread testThread =
+        new Thread(
+            () -> {
+              try {
+                RowMeta rowMeta = new RowMeta();
+                rowMeta.addValueMeta(new ValueMetaInteger("id"));
+
+                // Start with a small table to trigger resize quickly
+                ByteArrayHashIndex hashIndex = new ByteArrayHashIndex(rowMeta, 
2);
+
+                // Insert enough entries to fill and resize multiple times
+                for (int i = 0; i < 100; i++) {
+                  byte[] key = RowMeta.extractData(rowMeta, new Object[] 
{(long) i});
+                  hashIndex.put(key, new byte[] {(byte) i});
+                }
+
+                done.countDown();
+              } catch (Throwable t) {
+                error.set(t);
+                done.countDown();
+              }
+            });
+
+    testThread.start();
+
+    boolean completed = done.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+    if (!completed) {
+      testThread.interrupt();
+      fail(
+          "put() appears to hang - possible infinite loop (did not complete 
within "
+              + TIMEOUT_SECONDS
+              + " seconds)");
+    }
+
+    if (error.get() != null) {
+      fail("Test failed with exception: " + error.get().getMessage());
+    }
+  }
 }

Reply via email to