Apache Hop version?
2.18.0
Java version?
21
Operating system
Windows
What happened?
Problem Statement
The Stream Lookup transform is known to cause pipeline deadlocks when processing large datasets with split/rejoin streams [0][1]. The root cause is that when one input stream is blocked due to a full rowset buffer (e.g., after a Group By transform), Stream Lookup waits for that stream while the upstream transform cannot proceed, creating a cyclic dependency.
Current official solutions [2] require users to manually adjust pipeline design (increase rowset size, separate streams, split pipelines, or use Blocking transforms). These are workarounds, not root-cause fixes.
Proposed Solution
Use separate threads (regular threads or virtual threads, depending on the runtime environment) to read each info stream independently and concurrently, with a CountDownLatch to coordinate the main thread. This approach eliminates deadlocks because each stream is consumed in its own thread, preventing buffer contention between upstream transforms.
Pseudo‑code (using regular threads for compatibility with Java 11):
private boolean loadInfoStream(IStream stream, CountDownLatch latch, List<Exception> errors) {
Thread loader = new Thread(() -> {
try {
IRowSet rowSet = findInputRowSet(stream.getTransformName());
Object[] row = getRowFrom(rowSet);
while (row != null && !isStopped()) {
// Add to cache (needs thread-safe collection)
data.cache.add(row);
row = getRowFrom(rowSet);
}
} catch (Exception e) {
errors.add(e);
} finally {
latch.countDown();
}
});
loader.setName("StreamLookup-Loader-" + stream.getTransformName());
loader.setUncaughtExceptionHandler((t, e) -> errors.add(e));
loader.start();
return true;
}
// In before processingrows():
CountDownLatch latch = new CountDownLatch(infoStreams.size());
List<Exception> errors = Collections.synchronizedList(new ArrayList<>());
for (IStream stream : infoStreams) {
loadInfoStream(stream, latch, errors);
}
latch.await(); // wait for all loading threads to finish
if (!errors.isEmpty()) {
throw new HopException("Failed to load lookup data", errors.get(0));
}
Key benefits:
No deadlock – each stream is read independently, no circular blocking.
Transparent to users – no pipeline redesign needed.
Compatible with Java 11 – regular threads work, virtual threads can be used as an optimisation if Java 21+ is available.
Root Cause – A Real Scenario
Consider a simple pipeline:
- Data Input produces 20,000 rows, split into two streams.
- Transform1 and Transform2 each receive 10,000 rows (but could be more).
- Both write into separate rowsets (
Rowset1 and Rowset2), each with a default capacity of 10,000 rows.
- StreamLookup consumes these rowsets sequentially with a single thread (first
Rowset1, then Rowset2).
What happens:
- Both transforms write 10,000 rows into their respective rowsets — both are now full.
- StreamLookup starts reading
Rowset1 first.
- Data Input continues to send more rows to both transforms.
- Transform1 keeps writing (because StreamLookup is consuming
Rowset1, freeing space).
- Transform2 tries to write the 10,001st row into
Rowset2, but the rowset is full and no one is consuming it → Transform2 blocks.
- StreamLookup is still busy consuming
Rowset1 and may never get to Rowset2.
- Result: Deadlock — Transform2 waits for StreamLookup to consume
Rowset2, StreamLookup is stuck on Rowset1, and the entire pipeline is hung.
This is not a theoretical problem — it is a reproducible deadlock caused by the single‑threaded consumer pattern in StreamLookup.
References
[0] https://hop.apache.org/manual/2.18.0/how-to-guides/avoiding-deadlocks.html
[1] #3740
[2] https://hop.incubator.apache.org/manual/2.18.0/pipeline/transforms/streamlookup.html
es.
Issue Priority
Priority: 2
Issue Component
Component: Transforms
Apache Hop version?
2.18.0
Java version?
21
Operating system
Windows
What happened?
Problem Statement
The Stream Lookup transform is known to cause pipeline deadlocks when processing large datasets with split/rejoin streams [0][1]. The root cause is that when one input stream is blocked due to a full rowset buffer (e.g., after a Group By transform), Stream Lookup waits for that stream while the upstream transform cannot proceed, creating a cyclic dependency.
Current official solutions [2] require users to manually adjust pipeline design (increase rowset size, separate streams, split pipelines, or use Blocking transforms). These are workarounds, not root-cause fixes.
Proposed Solution
Use separate threads (regular threads or virtual threads, depending on the runtime environment) to read each info stream independently and concurrently, with a
CountDownLatchto coordinate the main thread. This approach eliminates deadlocks because each stream is consumed in its own thread, preventing buffer contention between upstream transforms.Pseudo‑code (using regular threads for compatibility with Java 11):
Key benefits:
No deadlock – each stream is read independently, no circular blocking.
Transparent to users – no pipeline redesign needed.
Compatible with Java 11 – regular threads work, virtual threads can be used as an optimisation if Java 21+ is available.
Root Cause – A Real Scenario
Consider a simple pipeline:
Rowset1andRowset2), each with a default capacity of 10,000 rows.Rowset1, thenRowset2).What happens:
Rowset1first.Rowset1, freeing space).Rowset2, but the rowset is full and no one is consuming it → Transform2 blocks.Rowset1and may never get toRowset2.Rowset2, StreamLookup is stuck onRowset1, and the entire pipeline is hung.This is not a theoretical problem — it is a reproducible deadlock caused by the single‑threaded consumer pattern in StreamLookup.
References
[0] https://hop.apache.org/manual/2.18.0/how-to-guides/avoiding-deadlocks.html
[1] #3740
[2] https://hop.incubator.apache.org/manual/2.18.0/pipeline/transforms/streamlookup.html
es.
Issue Priority
Priority: 2
Issue Component
Component: Transforms