The interrupt status of a worker thread in the standard ExecutorService
implementations will be cleared when a task is completed, and it's ignored for the purposes of testing whether a task was canceled before completion (as detected by Future.isCancelled()
).
So, you can safely throw an unchecked ("runtime") exception rather than InterruptedException
, and it won't interfere with the operation of the ExecutorService
.
However, if you are still concerned, you can preserve the interrupt status by re-asserting it:
if (Thread.interrupted()) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Time limit exceeded.");
}
Or using a different method that doesn't clear it:
if (Thread.currentThread().isInterrupted())
throw new IllegalStateException("Time limit exceeded.");
Normally custom runtime exceptions are a bad idea, because they can result in leaky abstractions, but here, a custom type in place of IllegalStateException
may make it cleaner for you to distinguish your timeout exceptions from runtime exceptions raised by the student's code.
Update: Canceling a task running with ExecutorService
is only reliable if the task is written correctly to support interruption, and you can't trust students to do that.
So your options are:
- Manually review the source code, inserting interruption detection where necessary.
- Get rid of
ExecutorService
and create worker threads yourself. Use the stop()
or stop(Throwable)
method to terminate the threads after timeout. To be truly robust, use a byte code engineering library like ASM to automate analysis of the compiled plugin code to see if it can catch the Throwable
(or ThreadDeath
or the custom Throwable
) you are using to stop()
the worker thread.
- Fork Java processes to run the agent code, and kill the whole process after timeout.