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:
dragonmacher
2019-05-09 17:57:47 -04:00
parent 16a7aa5b85
commit 2497a347ea
2 changed files with 55 additions and 42 deletions

View File

@@ -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() {

View File

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