3

NB This is not about terminating a worker thread. It's about the aftermath of a normal termination.

I'm calling execute in the EDT, so this spawns a "worker thread".
I'm calling worker.get() in a Callable which I submit to an ExecutorService, specifically a ThreadPoolExecutor.

The task ends OK (i.e. worker.get() returns with a result, and the Callable also ends OK).

I call shutdown() on the Executor Service: method terminates normally.

The puzzle comes when I examine the threads after this. This is in the context of unit testing, and I basically want to kill off all the activity which has been started during the last test.

I'm listing all the threads, and in addition to the various ones which were present before the test started I see this:

# thread Thread[pool-2-thread-1,5,main], state WAITING, alive True, interrupted False, thread group main
# thread Thread[SwingWorker-pool-4-thread-1,5,main], state WAITING, alive True, interrupted False, thread group main

The trouble is, as the tests get run, the newly constructed "Application" object makes a new thread pool each time, and more and more SwingWorkers get created... and none of these threads appears to transition to the Thread.State "TERMINATED" (which presumably would mean they would then cease to be listed).

I then tried calling join on the "SwingWorker-pool-4-thread" Thread... but this causes my current thread to hang.

Can anyone explain what's going on here and how to transition these threads to "TERMINATED"?

later

Response to 2 helpful comments. 10 threads, right. My thinking is just that a test if it fails (or an exception occurs) this can leave things in a troublesome state: suppose, in a functional test, 3 SWs are involved, and all sorts of publish/process stuff is going on, not to mention various Runnables in the EDT. Before moving to the next test I want everything to be shut, closed, ended, killed, returned to a pristine, virginal state!

To ensure all Runnables have ended we have Robot.waitForIdle(). I've also thought up a strategy for ensuring the publish/process thing has ended: SwingWorker, done() is executed before process() calls are finished... . But in fact the latter, useful as it is, presupposes that the testing code knows which SWs to call the method on... I'm looking for a blunderbus, which will just kill em stone dead.

The toString for these Thread objects shows clearly which has been created since starting the latest test... but I don't know how to kill 'em off. Isn't it the case that "WAITING" means that, vampire-like, they might rise up again and do all sorts of bad things? And just leaving vast numbers of "waiting" Threads hanging around, potentially running into their hundreds, seems a bit... untidy!

even later

Ah yes, getting hold of the AppContext object is vital because you need to "reset" this in order to prevent RejectedExecutionException. See my answer for how to do in Jython.

I also note that in the SW API doc it says

Because SwingWorker implements Runnable, a SwingWorker can be submitted to an Executor for execution.

This presumably means that you don't have to use SW's default executor at all. Might be the way to go, but unless needed for another reason in the app code, it kind of violates the principle of not tailoring the app code for the convenience of the testing code...

Community
  • 1
  • 1
mike rodent
  • 14,126
  • 11
  • 103
  • 157
  • 1
    SwingWoker has its own ExecutorService (with a fixed pool of 10 threads), which all SwingWorkers use, these are daemon threads, so they won't stop the JVM from exiting normally. Typically, you don't need to worry about them – MadProgrammer Feb 22 '16 at 20:02
  • Specifically in the context of unit testing... you have two interacting bits of code: there's the code that creates and submits the tasks to the Executor, and then there's the code that's actually *in* the task. I would design (at least) one unit test for the task itself, and another unit test for the task-submitter; in this latter unit test I would mock the ExecutorService itself. The task test will verify behaviour of the task. – dcsohl Feb 22 '16 at 20:03
  • Thanks to both... see additional note. – mike rodent Feb 22 '16 at 20:19
  • AFAIK `WAITING` means they are doing nothing. In the case of the `SwingWorker`, these threads are pooled, so, yes, the worker will try and re-use them if it can. Because in the "normal" operations, the worker needs to deal with the fact that the `doInBackground` method might explode horrible, it's already designed to deal with it (which is why `get` throws an `Exception`) – MadProgrammer Feb 23 '16 at 02:31

2 Answers2

5

Disclaimer: I'm not sure I see the need to do this, but...

Having dug through the SwingWorker source code, the worker uses the private getWorkersExecutorService method to get the ExecutorService for the current JVM.

This uses the AppContext to store an instance of ExecutorService, from which all instances of SwingWorker (for the JVM) will draw threads from to execute their functionality.

So, if you do something like...

AppContext appContext = AppContext.getAppContext();
ExecutorService executorService = (ExecutorService) appContext.get(SwingWorker.class);
System.out.println(executorService);

will print out something like...

java.util.concurrent.ThreadPoolExecutor@4554617c[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]

(this assumes you've previously started a SwingWorker otherwise it will return null)

But, what does this mean? Well, we can now gain direct access the ExecutorService for all the SwingWorkers (the apocalypse may now start)

This means you can shutdown the service and even remove it from the AppContext, for example...

SwingWorker worker = new SwingWorker() {
    @Override
    protected Object doInBackground() throws Exception {
        System.out.println("Starting");
        Thread.sleep(10000);
        System.out.println("Ending");
        return null;
    }
};
worker.execute();
synchronized (SwingWorker.class) {
    AppContext appContext = AppContext.getAppContext();
    ExecutorService executorService = (ExecutorService) appContext.get(SwingWorker.class);
    System.out.println(executorService);
    System.out.println("Shutting down");
    executorService.shutdownNow();
    try {
        System.out.println("Waiting");
        executorService.awaitTermination(Integer.MAX_VALUE, TimeUnit.DAYS);
        System.out.println("Done");
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
    appContext.remove(SwingWorker.class);
}

In my testing, it outputs...

java.util.concurrent.ThreadPoolExecutor@4554617c[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
Shutting down
Waiting
Starting
Done

So the worker doesn't even get a chance to start. If I place a delay in (after calling execute), shutdownNow basically hammers it and won't wait for the worker to finish, but if I use shutdown it does, so, there you have it.

I should add, AFAIK WAITING means they are doing nothing. In the case of the SwingWorker, these threads are pooled, so, yes, the worker will try and re-use them if it can, BUT because in the "normal" operations of the SwingWorker, the worker needs to deal with the fact that the doInBackground method might explode horrible, it's already designed to deal with it (which is why get throws an Exception), so unless you have a particular use case, I'm not really sure you need to go to this much trouble, just saying

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
1

This is in response to MadProgrammer's answer, for which I thank him/her.

Jython users can access and use private methods. Below, to get all threads, and also to get the SW's ExecutorService.

Running the code below shows, indeed, that the "SwingWorker..." thread disappears from the listing of threads at the end due to the shutdownNow. This doesn't stop 3 new threads persisting at the end, "TimerQueue", "AWT-Windows" and "Java2D Disposer"... but these are all in the "system" ThreadGroup, and the only "main" group thread remaining is the current thread, so I'd say problem (if there is one) solved!

def get_all_threads():
    private_static_method = java.lang.Thread.getDeclaredMethod( 'getThreads' )
    private_static_method.accessible = True
    # invoking a static method: one param, None
    return private_static_method.invoke( None )

def show_all_threads():
    for thread in get_all_threads():
        print( '  # ID %d: thread %s, state %s, alive %s, interrupted %s, thread group %s' % ( 
            thread.id, thread, thread.state, thread.alive, thread.isInterrupted(), thread.threadGroup.name,) )
        if thread is java.lang.Thread.currentThread():
            print( '  # --> current thread' )

class Worker( javax.swing.SwingWorker ):
    def doInBackground( self ):
        print( 'Starting' )
        time.sleep( 3 )
        print( 'Ending' )

Worker().execute()

private_static_method = javax.swing.SwingWorker.getDeclaredMethod( 'getWorkersExecutorService' )
private_static_method.accessible = True
exec_serv = private_static_method.invoke( None )

show_all_threads()
exec_serv.shutdownNow()
print( '# shutDownNow ordered...' )
exec_serv.awaitTermination( java.lang.Integer.MAX_VALUE, java.util.concurrent.TimeUnit.DAYS )
print( '# ...terminated' )
show_all_threads()

# now "reset" the AppContext object so future executes won't raise a RejectedExecutionException
app_context = sun.awt.AppContext.getAppContext()
app_context.remove( javax.swing.SwingWorker )
mike rodent
  • 14,126
  • 11
  • 103
  • 157