Fixed lockup in incremental load job when the latch was not counted down

This commit is contained in:
dragonmacher 2025-06-09 17:59:39 -04:00
parent c0cfad9107
commit 748d4d037f
2 changed files with 16 additions and 9 deletions

View file

@ -35,10 +35,9 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
/**
* Used to signal that the updateManager has finished loading the final contents gathered
* by this job. By default, the value is 0, which means there is nothing to wait for. If we
* flush, this will be set to 1.
* by this job. This is also updated if this job is cancelled.
*/
private volatile CountDownLatch completedCallbackLatch = new CountDownLatch(0);
private volatile CountDownLatch completedCallbackLatch = new CountDownLatch(1);
private volatile boolean isCancelled = false;
private volatile IncrementalUpdatingAccumulator incrementalAccumulator;
@ -141,13 +140,18 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
// -A block on jobDone() can now complete as we release the lock
// -jobDone() will notify listeners in an invokeLater(), which puts it behind ours
//
completedCallbackLatch = new CountDownLatch(1);
Swing.runLater(() -> updateManager.addThreadedTableListener(IncrementalLoadJob.this));
}
waitForThreadedTableUpdateManagerToFinish();
}
/**
* Waits for the final flushed data to be added to the table. We will get called when the data
* is finished loading or cancelled. The latch will also be released if the cancel method of
* this job is called. This can happen if the work queue is told to cancel all jobs, which can
* happen if a new reload job is requested.
*/
private void waitForThreadedTableUpdateManagerToFinish() {
try {
completedCallbackLatch.await();
@ -179,6 +183,7 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
super.cancel();
isCancelled = true;
incrementalAccumulator.cancel();
completedCallbackLatch.countDown();
// Note: cannot do this here, since the cancel() call may happen asynchronously and after
// a call to reload() on the table model. Assume that the model itself has already

View file

@ -56,7 +56,7 @@ import ghidra.util.task.TaskMonitor;
* <pre>
* {@literal QCallback<ITEM, RESULT> callback = new AbstractQCallback<ITEM, RESULT>()} {
* public RESULT process(ITEM item, TaskMonitor monitor) {
* // do work here...
* // do work here and create a RESULT item for later processing...
* }
* };
*
@ -85,7 +85,7 @@ import ghidra.util.task.TaskMonitor;
* <pre>{@literal
* QCallback<ITEM, RESULT> callback = new AbstractQCallback<ITEM, RESULT>() {
* public RESULT process(ITEM item, TaskMonitor monitor) {
* // do work here...
* // do work here and create a RESULT item for later processing...
* }
* };}
*
@ -652,6 +652,7 @@ public class ConcurrentQ<I, R> {
}
}
// Called by the FutureTaskMonitor that we gave to the thread pool
private class CallbackCallable implements Callable<R> {
private I item;
@ -663,6 +664,7 @@ public class ConcurrentQ<I, R> {
@Override
public R call() throws Exception {
// callback is the client callback given to this queue at construction
return callback.process(item, future);
}