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());
+ }
+ }
}