Reputation: 2823
in some of my jUnit tests I create a ThreadPoolExecutor to test if my code does not have obvious concurrency errors. At the end of each such test I shutdown the executor and verify if all tasks were completed, similarly to the below code:
// wait on a conditional that indicates that results are available
executor.shutdown();
executor.awaitTermination(100l);
// do other result verifications here
if ( ! executor.isTerminated()) {
final var stuckTasks = executor.shutdownNow();
for (var stuckTask: stuckTasks) log.severe("stuck " + stuckTask);
fail("executor not terminated, " + stuckTasks.size() + " tasks remaining");
}
If I run these tests in a loop, every few hours I get a failure with message "executor not terminated, 0 tasks remaining"
Is it normal and can be safely ignored or does it mean that my code has some concurrency errors in fact?
Apart from unfinished tasks, are there any other possible reasons for executor to not terminate?
I'd like to emphasize that it has NEVER happened for the number of remaining tasks to be nonzero (even after running the tests in a loop for more than 12 hours, where a single run takes about 2s) and all other verifications pass correctly, which would be impossible if any of the tasks submitted to this executor actually got stuck.
100ms that I await for termination is a lot in this case as all the submitted tasks take less than 10ms and they are all supposed to be finished even before awaitTermination(100l)
is called as results were already available.
I'm using openJdk-11 on ubuntu in case it matters.
The code in question is here if anyone is interested (I have since modified it to fail only if the number of remaining tasks is nonzero, as so far it seems harmless). Failure was happening only on userExecutor
(never on grpcExecutor
, which makes it more strange) in various random test methods in this class.
Thanks!
update:
As @Thomas pointed in the comment, shutdownNow()
returns only tasks that were not even started. In addition to this, getActiveCount()
should be checked also.
Upvotes: 1
Views: 986
Reputation: 88747
To summarize the comments:
ThreadPoolExecutor
maintains a queue of not yet and shutdownNow()
drains that queue and returns its contents. That means you'll only get the tasks that have not started yet.
This is consistent with the Javadoc which states:
Returns: list of tasks that never commenced execution.
Additionally, there is a set of workers in ThreadPoolExecutor
(the actual thread pool) which can be queried for the number of active tasks using ThreadPoolExecutor.getActiveCount()
. That method basically queries each workder to see if it currently has a lock which indicates it is executing a task.
For a more complete picture, have a look at ThreadPoolExecutor.getTaskCount()
. It adds up a couple of different numbers:
shutdownNow()
.It would be great if we'd get access to what the workers are currently executing but I didn't find a way since workers
(the set of workers) isn't exposed to the outside world and is private and workers don't even seem to hold a reference to the tasks they're executing (at least not directly).
Now shutdown()
tries to interrupt idle workers, not active ones so awaitTermination()
might time out due to workers still being active. On the other hand shutdownNow()
interrupts all workers.
Upvotes: 2