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 * 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 * by this job. This is also updated if this job is cancelled.
* flush, this will be set to 1.
*/ */
private volatile CountDownLatch completedCallbackLatch = new CountDownLatch(0); private volatile CountDownLatch completedCallbackLatch = new CountDownLatch(1);
private volatile boolean isCancelled = false; private volatile boolean isCancelled = false;
private volatile IncrementalUpdatingAccumulator incrementalAccumulator; private volatile IncrementalUpdatingAccumulator incrementalAccumulator;
@ -140,14 +139,19 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
// -We release the lock // -We release the lock
// -A block on jobDone() can now complete as we release the lock // -A block on jobDone() can now complete as we release the lock
// -jobDone() will notify listeners in an invokeLater(), which puts it behind ours // -jobDone() will notify listeners in an invokeLater(), which puts it behind ours
// //
completedCallbackLatch = new CountDownLatch(1);
Swing.runLater(() -> updateManager.addThreadedTableListener(IncrementalLoadJob.this)); Swing.runLater(() -> updateManager.addThreadedTableListener(IncrementalLoadJob.this));
} }
waitForThreadedTableUpdateManagerToFinish(); 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() { private void waitForThreadedTableUpdateManagerToFinish() {
try { try {
completedCallbackLatch.await(); completedCallbackLatch.await();
@ -179,6 +183,7 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
super.cancel(); super.cancel();
isCancelled = true; isCancelled = true;
incrementalAccumulator.cancel(); incrementalAccumulator.cancel();
completedCallbackLatch.countDown();
// Note: cannot do this here, since the cancel() call may happen asynchronously and after // 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 // a call to reload() on the table model. Assume that the model itself has already

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -56,7 +56,7 @@ import ghidra.util.task.TaskMonitor;
* <pre> * <pre>
* {@literal QCallback<ITEM, RESULT> callback = new AbstractQCallback<ITEM, RESULT>()} { * {@literal QCallback<ITEM, RESULT> callback = new AbstractQCallback<ITEM, RESULT>()} {
* public RESULT process(ITEM item, TaskMonitor monitor) { * 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 * <pre>{@literal
* QCallback<ITEM, RESULT> callback = new AbstractQCallback<ITEM, RESULT>() { * QCallback<ITEM, RESULT> callback = new AbstractQCallback<ITEM, RESULT>() {
* public RESULT process(ITEM item, TaskMonitor monitor) { * 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 class CallbackCallable implements Callable<R> {
private I item; private I item;
@ -663,6 +664,7 @@ public class ConcurrentQ<I, R> {
@Override @Override
public R call() throws Exception { public R call() throws Exception {
// callback is the client callback given to this queue at construction
return callback.process(item, future); return callback.process(item, future);
} }