mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
Threaded Tables - fixed timing bug that caused a 'pending job' to never
get run if the thread finished with a new pending job being scheduled before the 'notify finished' happened
This commit is contained in:
@@ -204,7 +204,8 @@ public class TableUpdateJob<T> {
|
|||||||
* @return true if the sort can be processed by this job, false if this job is essentially already
|
* @return true if the sort can be processed by this job, false if this job is essentially already
|
||||||
* completed and therefor cannot perform the sort job.
|
* completed and therefor cannot perform the sort job.
|
||||||
*/
|
*/
|
||||||
public synchronized boolean requestSort(TableSortingContext<T> newSortingContext, boolean forceSort) {
|
public synchronized boolean requestSort(TableSortingContext<T> newSortingContext,
|
||||||
|
boolean forceSort) {
|
||||||
if (currentState == DONE) {
|
if (currentState == DONE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -688,7 +689,7 @@ public class TableUpdateJob<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Job - [state history=\n" + getStateHistoryString() + "]";
|
return getClass().getSimpleName() + " - [state history=\n" + getStateHistoryString() + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getStateHistoryString() {
|
private String getStateHistoryString() {
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import ghidra.util.task.*;
|
|||||||
* in a ThreadedTableModel occur, this class schedules a TableUpdateJob to do the work. It uses
|
* in a ThreadedTableModel occur, this class schedules a TableUpdateJob to do the work. It uses
|
||||||
* a SwingUpdateManager to coalesce add/remove so that the table does not constantly update when
|
* a SwingUpdateManager to coalesce add/remove so that the table does not constantly update when
|
||||||
* are large number of table changing events are incoming.
|
* are large number of table changing events are incoming.
|
||||||
|
*
|
||||||
|
* @param <T> the row type
|
||||||
*/
|
*/
|
||||||
class ThreadedTableModelUpdateMgr<T> {
|
class ThreadedTableModelUpdateMgr<T> {
|
||||||
static final int TOO_MANY_ADD_REMOVES = 3000;
|
static final int TOO_MANY_ADD_REMOVES = 3000;
|
||||||
@@ -38,7 +40,7 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
public final static int MAX_DELAY = 1000 * 60 * 20;
|
public final static int MAX_DELAY = 1000 * 60 * 20;
|
||||||
|
|
||||||
private ThreadedTableModel<T, ?> model;
|
private ThreadedTableModel<T, ?> model;
|
||||||
private SwingUpdateManager updateManager;
|
private SwingUpdateManager addRemoveUpdater;
|
||||||
private TaskMonitor monitor;
|
private TaskMonitor monitor;
|
||||||
|
|
||||||
// weakly consistent iterator so that clients can remove listeners on notification
|
// weakly consistent iterator so that clients can remove listeners on notification
|
||||||
@@ -70,7 +72,7 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
"the given task monitor must be cancel enabled (e.g., you cannot use " +
|
"the given task monitor must be cancel enabled (e.g., you cannot use " +
|
||||||
"the TaskMonitorAdapter.DUMMY_MONITOR, as that is not cancelleable)");
|
"the TaskMonitorAdapter.DUMMY_MONITOR, as that is not cancelleable)");
|
||||||
|
|
||||||
updateManager = new SwingUpdateManager(DELAY, MAX_DELAY, () -> processAddRemoveItems());
|
addRemoveUpdater = new SwingUpdateManager(DELAY, MAX_DELAY, () -> processAddRemoveItems());
|
||||||
|
|
||||||
notifyPending = () -> {
|
notifyPending = () -> {
|
||||||
for (ThreadedTableModelListener listener : listeners) {
|
for (ThreadedTableModelListener listener : listeners) {
|
||||||
@@ -110,7 +112,7 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object getSynchronizingLock() {
|
Object getSynchronizingLock() {
|
||||||
return updateManager;
|
return addRemoveUpdater;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,15 +120,17 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
* Called when the swing update manager decides its time to run.
|
* Called when the swing update manager decides its time to run.
|
||||||
*/
|
*/
|
||||||
private void processAddRemoveItems() {
|
private void processAddRemoveItems() {
|
||||||
synchronized (updateManager) {
|
synchronized (addRemoveUpdater) {
|
||||||
if (addRemoveWaitList.isEmpty()) {
|
if (addRemoveWaitList.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// too many requests for add/remove triggers a full reload for efficiency
|
// too many requests for add/remove triggers a full reload for efficiency
|
||||||
if (addRemoveWaitList.size() > maxAddRemoveCount) {
|
if (addRemoveWaitList.size() > maxAddRemoveCount) {
|
||||||
reload();
|
reload();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingJob = new AddRemoveJob<>(model, addRemoveWaitList, monitor);
|
pendingJob = new AddRemoveJob<>(model, addRemoveWaitList, monitor);
|
||||||
addRemoveWaitList = new ArrayList<>();
|
addRemoveWaitList = new ArrayList<>();
|
||||||
runJob();
|
runJob();
|
||||||
@@ -138,13 +142,15 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
* work that is built into the pending job.
|
* work that is built into the pending job.
|
||||||
*/
|
*/
|
||||||
private void runJob() {
|
private void runJob() {
|
||||||
synchronized (updateManager) {
|
synchronized (addRemoveUpdater) {
|
||||||
if (thread != null) {
|
if (thread != null) {
|
||||||
return; // if thread exists, it will handle any pending job.
|
return; // if thread exists, it will handle any pending job
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pendingJob == null) {
|
if (pendingJob == null) {
|
||||||
return; // nothing to do!
|
return; // nothing to do!
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ok, we have to create a thread to process the pending job
|
// Ok, we have to create a thread to process the pending job
|
||||||
thread = new Thread(threadRunnable,
|
thread = new Thread(threadRunnable,
|
||||||
"Threaded Table Model Update Manager: " + model.getName());
|
"Threaded Table Model Update Manager: " + model.getName());
|
||||||
@@ -161,8 +167,8 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
* things.
|
* things.
|
||||||
*/
|
*/
|
||||||
public void cancelAllJobs() {
|
public void cancelAllJobs() {
|
||||||
synchronized (updateManager) {
|
synchronized (addRemoveUpdater) {
|
||||||
updateManager.stop();
|
addRemoveUpdater.stop();
|
||||||
if (currentJob != null) {
|
if (currentJob != null) {
|
||||||
currentJob.cancel();
|
currentJob.cancel();
|
||||||
}
|
}
|
||||||
@@ -176,7 +182,7 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
* is performed. It also is called if too many add/removes have been accumulated.
|
* is performed. It also is called if too many add/removes have been accumulated.
|
||||||
*/
|
*/
|
||||||
void reload() {
|
void reload() {
|
||||||
synchronized (updateManager) {
|
synchronized (addRemoveUpdater) {
|
||||||
cancelAllJobs();
|
cancelAllJobs();
|
||||||
pendingJob = new LoadJob<>(model, monitor);
|
pendingJob = new LoadJob<>(model, monitor);
|
||||||
runJob();
|
runJob();
|
||||||
@@ -184,7 +190,7 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void reloadSpecificData(List<T> data) {
|
void reloadSpecificData(List<T> data) {
|
||||||
synchronized (updateManager) {
|
synchronized (addRemoveUpdater) {
|
||||||
cancelAllJobs();
|
cancelAllJobs();
|
||||||
TableData<T> tableData = TableData.createFullDataset(data);
|
TableData<T> tableData = TableData.createFullDataset(data);
|
||||||
pendingJob = new LoadSpecificDataJob<>(model, monitor, tableData);
|
pendingJob = new LoadSpecificDataJob<>(model, monitor, tableData);
|
||||||
@@ -209,19 +215,18 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
* to be re-sorted.
|
* to be re-sorted.
|
||||||
*/
|
*/
|
||||||
void sort(TableSortingContext<T> sortingContext, boolean forceSort) {
|
void sort(TableSortingContext<T> sortingContext, boolean forceSort) {
|
||||||
synchronized (updateManager) {
|
synchronized (addRemoveUpdater) {
|
||||||
if (currentJob != null && pendingJob == null &&
|
if (currentJob != null && pendingJob == null &&
|
||||||
currentJob.requestSort(sortingContext, forceSort)) {
|
currentJob.requestSort(sortingContext, forceSort)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pendingJob != null) {
|
if (pendingJob != null && pendingJob.requestSort(sortingContext, forceSort)) {
|
||||||
pendingJob.requestSort(sortingContext, forceSort);
|
return;
|
||||||
}
|
|
||||||
else {
|
|
||||||
pendingJob = new SortJob<>(model, monitor, sortingContext, forceSort);
|
|
||||||
runJob();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pendingJob = new SortJob<>(model, monitor, sortingContext, forceSort);
|
||||||
|
runJob();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,17 +244,17 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
* start a thread to do the work.
|
* start a thread to do the work.
|
||||||
*/
|
*/
|
||||||
void filter() {
|
void filter() {
|
||||||
synchronized (updateManager) {
|
synchronized (addRemoveUpdater) {
|
||||||
if (currentJob != null && pendingJob == null && currentJob.requestFilter()) {
|
if (currentJob != null && pendingJob == null && currentJob.requestFilter()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (pendingJob != null) {
|
|
||||||
pendingJob.requestFilter();
|
if (pendingJob != null && pendingJob.requestFilter()) {
|
||||||
}
|
return;
|
||||||
else {
|
|
||||||
pendingJob = new FilterJob<>(model, monitor);
|
|
||||||
runJob();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pendingJob = new FilterJob<>(model, monitor);
|
||||||
|
runJob();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +268,7 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
* @param item the add/remove item to process.
|
* @param item the add/remove item to process.
|
||||||
*/
|
*/
|
||||||
void addRemove(AddRemoveListItem<T> item) {
|
void addRemove(AddRemoveListItem<T> item) {
|
||||||
synchronized (updateManager) {
|
synchronized (addRemoveUpdater) {
|
||||||
if (pendingJob != null) {
|
if (pendingJob != null) {
|
||||||
pendingJob.addRemove(item, maxAddRemoveCount);
|
pendingJob.addRemove(item, maxAddRemoveCount);
|
||||||
return;
|
return;
|
||||||
@@ -279,7 +284,7 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addRemoveWaitList.add(item);
|
addRemoveWaitList.add(item);
|
||||||
updateManager.update();
|
addRemoveUpdater.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,20 +294,20 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
* @return true if there is work to be done.
|
* @return true if there is work to be done.
|
||||||
*/
|
*/
|
||||||
boolean isBusy() {
|
boolean isBusy() {
|
||||||
synchronized (updateManager) {
|
synchronized (addRemoveUpdater) {
|
||||||
return thread != null || pendingJob != null || updateManager.isBusy() ||
|
return thread != null || pendingJob != null || addRemoveUpdater.isBusy() ||
|
||||||
!addRemoveWaitList.isEmpty();
|
!addRemoveWaitList.isEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the delay for the swing update manager.
|
* Sets the delay for the swing update managers
|
||||||
* @param updateDelayMillis the new delay for the swing update manager
|
* @param updateDelayMillis the new delay for the swing update manager
|
||||||
* @param maxUpdateDelayMillis the new max update delay; updates will not wait past this time
|
* @param maxUpdateDelayMillis the new max update delay; updates will not wait past this time
|
||||||
*/
|
*/
|
||||||
void setUpdateDelay(int updateDelayMillis, int maxUpdateDelayMillis) {
|
void setUpdateDelay(int updateDelayMillis, int maxUpdateDelayMillis) {
|
||||||
updateManager.dispose();
|
addRemoveUpdater.dispose();
|
||||||
updateManager = new SwingUpdateManager(updateDelayMillis, maxUpdateDelayMillis,
|
addRemoveUpdater = new SwingUpdateManager(updateDelayMillis, maxUpdateDelayMillis,
|
||||||
() -> processAddRemoveItems());
|
() -> processAddRemoveItems());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,12 +340,12 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
* Disposes the updateManager resource.
|
* Disposes the updateManager resource.
|
||||||
*/
|
*/
|
||||||
void dispose() {
|
void dispose() {
|
||||||
synchronized (updateManager) {
|
synchronized (addRemoveUpdater) {
|
||||||
listeners.clear();
|
listeners.clear();
|
||||||
monitor.cancel();
|
monitor.cancel();
|
||||||
monitor = new PermantentlyCancelledMonitor();
|
monitor = new PermantentlyCancelledMonitor();
|
||||||
cancelAllJobs();
|
cancelAllJobs();
|
||||||
updateManager.dispose();
|
addRemoveUpdater.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,7 +353,7 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
* Kicks the swing update manager to immediately process any accumulated add/removes.
|
* Kicks the swing update manager to immediately process any accumulated add/removes.
|
||||||
*/
|
*/
|
||||||
public void updateNow() {
|
public void updateNow() {
|
||||||
updateManager.updateNow();
|
addRemoveUpdater.updateNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -356,9 +361,14 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
* @return the new current job.
|
* @return the new current job.
|
||||||
*/
|
*/
|
||||||
private TableUpdateJob<T> getNextJob() {
|
private TableUpdateJob<T> getNextJob() {
|
||||||
synchronized (updateManager) {
|
synchronized (addRemoveUpdater) {
|
||||||
currentJob = pendingJob;
|
currentJob = pendingJob;
|
||||||
pendingJob = null;
|
pendingJob = null;
|
||||||
|
|
||||||
|
if (currentJob == null) {
|
||||||
|
jobDone();
|
||||||
|
}
|
||||||
|
|
||||||
return currentJob;
|
return currentJob;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,7 +379,11 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
* to work pending.
|
* to work pending.
|
||||||
*/
|
*/
|
||||||
private void jobDone() {
|
private void jobDone() {
|
||||||
synchronized (updateManager) {
|
|
||||||
|
// This synchronized is not need, as this method is only called from within a
|
||||||
|
// synchronized block. But, it feels better to be explicit here to signal that
|
||||||
|
// synchronization is needed.
|
||||||
|
synchronized (addRemoveUpdater) {
|
||||||
|
|
||||||
boolean isCancelled = monitor.isCancelled();
|
boolean isCancelled = monitor.isCancelled();
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
@@ -393,7 +407,7 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
/**
|
/**
|
||||||
* Runnable used be new threads to run scheduled jobs.
|
* Runnable used be new threads to run scheduled jobs.
|
||||||
*/
|
*/
|
||||||
class ThreadRunnable implements Runnable {
|
private class ThreadRunnable implements Runnable {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
TableUpdateJob<T> job = getNextJob();
|
TableUpdateJob<T> job = getNextJob();
|
||||||
@@ -402,11 +416,9 @@ class ThreadedTableModelUpdateMgr<T> {
|
|||||||
job.run();
|
job.run();
|
||||||
|
|
||||||
// useful for debug
|
// useful for debug
|
||||||
// Msg.debug(this, "ran job: " + job);
|
// Msg.debug(this, "ran job: " + job);
|
||||||
job = getNextJob();
|
job = getNextJob();
|
||||||
|
|
||||||
}
|
}
|
||||||
jobDone();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user