Andrew Reid
Andrew Reid

Reputation: 173

Exception on SwingWorker is not catchable

I have been working with Java threads in order to provide a GUI platform for running processes in a pipeline. I've managed to work through a number of issues with SwingWorker, but this one is seemingly incomprehensible.

My SwingWorker looks like:

SwingWorker<Boolean,Object> worker = new SwingWorker<Boolean,Object>() {
         @Override 
        public Boolean doInBackground() {
             return launchBlockingPipelineProcess(process, instance, project, logger, state);
         }

         @Override
         protected void done(){
             boolean success = false;
             try{
                 success = get();
                 if (!success){
                     state.setTaskFailed(true);
                     }
                 if (process.getStatus().equals(Status.Interrupted)){
                     state.setTaskInterrupted(true);
                     }
             }catch (Exception ex){
                state.setTaskFailed(true); 
                }

             processCompleted(process, success, state);
         }

    };

I use this to run a Java process; a simplified version of the launching code is:

try{
    Class<?> target_class = Class.forName(main_class);

    CommandInstance instance = (CommandInstance)target_class.newInstance();
    CommandFunctions.ProcessState state = instance.execute(args);

}catch (InvocationTargetException e){
    throw new PipelineException("Java process '" + this.getName() + "." + uid + "' encountered exception: " + e.getCause().getMessage());
}catch (Exception e){
    throw new PipelineException("JavaProcess encountered exception invoking target: " + e.getMessage());
    }

The process itself has a try-catch block around code which loads some data from a file. However, despite being wrapped in two layers of try-catch blocks (actually three, if you count the done() method), when the loader throws an Exception it prints a stack trace, the Exception is not caught, and the SwingWorker thread hangs (the code halts so I can no longer force an interrupt).

This sort of Exception hanging has occurred in other circumstances as well; most puzzlingly, in other -- seemingly identical -- cases the Exception is caught and the thread exits gracefully.

I haven't been able to find much about this online, although I'll continue to search. I am no expert in Swing threading, so I was hoping someone might have an insight about this sort of issue. I'd love it to be a very stupid error on my part :)

EDIT: @Adrian, here is the stack trace. It seems to stall half way... very strange:

java.io.EOFException
    at java.io.RandomAccessFile.readFully(RandomAccessFile.java:399)
    at mgui.io.standard.nifti.Nifti1Dataset.readVolBlob(Nifti1Dataset.java:2179)
    at mgui.io.standard.nifti.Nifti1Dataset.readDoubleVol(Nifti1Dataset.java:1916)
    at mgui.io.standard.nifti.NiftiVolumeLoader.setGrid3DBlocking(NiftiVolumeLoader.java:186)
    at mgui.io.domestic.shapes.VolumeFileLoader.setGrid3D(VolumeFileLoader.java:237)
    at mgui.io.domestic.shapes.VolumeFileLoader.getGrid3D(VolumeFileLoader.java:139)
    at mgui.io.domestic.shapes.VolumeFileLoader.getGrid3D(VolumeFileLoader.java:97)
    at minc.MincFunctions.create_volume_atlas_masks(MincFunctions.java:5240)
    at minc.MincFunctions.run_command(MincFunctions.java:153)
    at mgui.command.CommandInstance.execute(CommandInstance.java:87)
    at mgui.pipelines.JavaProcess.run(JavaProcess.java:141)
    at mgui.pipelines.PipelineFunctions.launchBlockingPipelineProcess(PipelineFunctions.java:238)
    at mgui.pipelines.PipelineFunctions.launchPipelineProcess(PipelineFunctions.ja

EDIT2: Debugging in Eclipse, I can halt at a breakpoint on the line where it is thrown (well, one step before); the stack trace at that point is:

Nifti1Dataset.readVolBlob(short) line: 2179 
Nifti1Dataset.readDoubleVol(short) line: 1916   
NiftiVolumeLoader.setGrid3DBlocking(Grid3D, int, ProgressUpdater) line: 186 
NiftiVolumeLoader(VolumeFileLoader).setGrid3D(Grid3D, int, ProgressUpdater) line: 237   
NiftiVolumeLoader(VolumeFileLoader).getGrid3D(VolumeInputOptions, int, ProgressUpdater) line: 139   
NiftiVolumeLoader(VolumeFileLoader).getGrid3D(int) line: 97 
MincFunctions.create_volume_atlas_masks() line: 5278    
MincFunctions.run_command(String) line: 153 
MincFunctions(CommandInstance).execute(String[]) line: 87   
JavaProcess.run(String[], long) line: 141   
PipelineFunctions.launchBlockingPipelineProcess(PipelineProcessInstance, String, InterfaceProject, String, PipelineState) line: 238 
PipelineFunctions.launchPipelineProcess(PipelineProcessInstance, String, InterfaceProject, String, boolean, PipelineState) line: 78 
PipelineFunctions.launchPipelineProcess(PipelineProcessInstance, boolean, PipelineState) line: 52   
PipelineProcessInstance.launch(boolean) line: 187   
InterfacePipeline.launch(boolean) line: 388 
PipelineLauncher.doInBackground() line: 57  
PipelineLauncher.doInBackground() line: 1   
SwingWorker$1.call() line: 277  
FutureTask$Sync.innerRun() line: 303    
SwingWorker$2(FutureTask<V>).run() line: 138    
PipelineLauncher(SwingWorker<T,V>).run() line: 316  
ThreadPoolExecutor$Worker.runTask(Runnable) line: 886   
ThreadPoolExecutor$Worker.run() line: 908   
Thread.run() line: 662  

Upvotes: 3

Views: 1093

Answers (3)

Andrew Reid
Andrew Reid

Reputation: 173

@Adrian, thanks for the suggestion; I do not think I am making any UI updates directly from the SwingWorker thread; I do make updates to JTree nodes to indicate the success or failure of a process, but I use the publish/process mechanism for this, which should ensure that all UI updates are called from the EDT:

These are listener handlers called from the executing pipeline:

@Override
public void pipelineTaskTerminated(DynamicPipelineEvent event, PipelineTask task) {
    publish(task);
}

@Override
public void pipelineTaskUpdated(DynamicPipelineEvent event, PipelineTask task) {
    // Here we can publish the update to a task status
    publish(task);

}

And here is the process method:

@Override
protected void process(List<PipelineTask> tasks) {

    // Update task listeners on the Event-Dispatch Thread
    for (int i = 0; i < tasks.size(); i++){
        PipelineTask task = tasks.get(i);
        InterfacePipeline pipeline = task.getPipeline();
        if (pipeline != null){
            task.fireStatusChanged();
            }
        }

}

Finally, here is how the JTree handles the event:

public void taskStatusChanged(PipelineTaskEvent e){
    if (model == null) return;
    model.nodeStructureChanged(this);
    tree.repaint();
}

As a (potentially informative) aside, this call to the JTree also does not result in an update of the UI, despite its being called from the EDT. The only way to force the tree to update is by clicking on the node itself. Possibly an issue for a new thread, but it may be related.

I'll go through the code and experiment by eliminating ANY potential UI updates from the thread, and see if the problem can be prevented that way... stay tuned :)

EDIT: Even removing these updates altogether I get the same Exception thrown...

Upvotes: 0

Adrian
Adrian

Reputation: 5681

I solved a similar table problem by not interfering when the AWT thread was painting the UI. I used invokeLater() to synchronize my thread with the UI thread.

See EventQueue for more details.

Upvotes: 0

mKorbel
mKorbel

Reputation: 109813

yes is possible take Exception(s) from SwingWorker's methods done(), but required strictly naming every thread, more in this thread, especially answer by @trashgod, and I haven't found another possibility of how to it another way

Upvotes: 2

Related Questions