3

I have a Java app that processes a datastream from data arriving via a serial port, and it displays a summary in a Swing UI.

It works fine, but when I set breakpoints in Eclipse in certain threads (e.g. the Swing event dispatch thread), I have a limited amount of time before the JVM crawls to a halt: the incoming data is still being processed, and some system queue, whether it's a data queue or an event queue, is getting overfilled.

Is there any way I can detect this in upstream threads, so that my upstream processing starts throwing away data during debugging?

If my program explicitly uses a queue, I can just throw away data when the queue size gets too high.

But I can't do this if the queue is "implicit", e.g. it's managed by some other piece of software beyond my direct control. I can think of two possibilities:

  1. If I'm using SwingUtilities.invokeLater(), or another UI framework that calls SwingUtilities.invokeLater(), how can I detect whether the Dispatch thread is backed up with events?

  2. If I'm using ExecutorService.submit(), how can I detect whether the executor's task queue is backed up?


update: I think I've solved #2 by wrapping my ExecutorService:

AbstractPipelineExecutor.java:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;

/**
 * pipeline executor
 */
abstract public class AbstractPipelineExecutor {
    /**
     * a pipeline scheduled item
     */
    public interface Task extends Runnable
    {
        /**
         * if item cannot be run, this is called instead
         */
        public void denied();
    }

    final private ExecutorService executor;

    public AbstractPipelineExecutor(ExecutorService executor)
    {
        this.executor = executor;
    }

    /**
     * submit an item to be executed
     * @param task pipeline item
     */
    public Future<?> submit(final Task task)
    {
        Future<?> result = null;
        if (this.executor.isShutdown())
        {
            task.denied();
        }
        else
        {
            try
            {
                onSubmit(task);
                result = this.executor.submit(new Runnable() {
                    @Override public void run()
                    {
                        onBeginExecute(task);
                        try
                        {
                            task.run();
                        }
                        catch (RuntimeException e)
                        {
                            onExecutionException(task, e);
                        }
                        finally
                        {
                            onEndExecute(task);
                        }
                    }
                });
            }
            catch (RejectedExecutionException e)
            {
                task.denied();
            }
        }
        return result;
    }


    /**
     * event handler: item is submitted
     * @param task pipeline item 
     */
    abstract protected void onSubmit(Task task) throws RejectedExecutionException;
    /**
     * event handler: item execution is begun
     * @param task pipeline item 
     */
    protected void onBeginExecute(Task task) {}
    /**
     * event handler: item throws a runtime exception
     * @param task pipeline item 
     */
    protected void onExecutionException(Task task, RuntimeException e) {
        throw(e);
    }
    /**
     * event handler: item execution is ended
     * @param task pipeline item 
     */
    protected void onEndExecute(Task task) {}
}

BoundedPipelineExecutor.java:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;

public class BoundedPipelineExecutor extends AbstractPipelineExecutor {

    public BoundedPipelineExecutor(ExecutorService executor, int bound) {
        super(executor);
        this.q = new ArrayBlockingQueue<Task>(bound);
    }

    final private ArrayBlockingQueue<Task> q; 

    @Override public void onSubmit(Task task)
    {
        if (!this.q.offer(task))
            throw new RejectedExecutionException(task.toString());
    }
    @Override public void onBeginExecute(Task task)
    {
        this.q.remove();
    }
}
Jason S
  • 184,598
  • 164
  • 608
  • 970

1 Answers1

1

Rather than using SwingUtilities.invokeLater directly from your IO classes, I'd suggest that they should update a 'summariser' that can make informed decisions about updating the UI. Even without debug breakpoints, you don't want heavy IO operations to flood your user interface.

So, create a class that receives and processes your data, and allow your user interface to poll that information at suitable intervals.

AndyT
  • 1,413
  • 9
  • 11
  • thanks, but (a) I'm already splitting my app into a model + view class, so I'm not invoking UI ops from my IO classes, and (b) UI polling or lack thereof isn't the issue. I have information that arrives and must be either processed + put into a queue, or thrown away. During normal operation it's fine. But when I am debugging it and stopped in a breakpoint, I get gridlock. – Jason S Mar 07 '11 at 13:59
  • 1
    Fair enough. Your description of the problem suggested that the Swing event dispatch thread was involved in clearing the queues in some way. Could you debug by creating a mock data source that can be replayed from real data - and stopped when necessary? – AndyT Mar 07 '11 at 14:13