0

I was surprised to find Java concurrency timeouts do not stop blocked socket read operation in the thread.

I was using Selenium RemoteWebDriver to simulate a load test scenario. I have wrapped the execution of Selenium commands in a Callable and used get() method with the timeout parameter. It works perfectly when there are less than 3 concurrent threads executing but the situation deteriorates when there are 4 or more concurrent threads. Some of the threads get stuck at socket read and are stuck for longer than the timeout setting on the Callable.

I did some research online, the root cause was a hard-coded socket timeout of 3 hours in Selenium code. There used to be a hack to overwrite the setting using reflection but with the latest version I don't think it's hackable any more.

I wonder whether there is a way to stop the thread that is IO blocked externally since I don't want to change Selenium code and end up having to maintain my own version of Selenium.

Here's how I handle the thread timeout in my code:

ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Long> future = null;
try {
    future = executorService.submit(new Callable<Long>() {
         @Override
         public Long call() throws Exception {
             return commandProcessor.process(row);
         }
     });   
timeTaken = future.get(currentTimeout, TimeUnit.MILLISECONDS);
executorService.shutdown();
} catch (Exception e) {
    log.error(e.getMessage(), e);
    // cancel the task
    if (future != null && !future.isDone()) {
         future.cancel(true);
    }
    executorService.shutdownNow();
}

And since it fails to timeout randomly, I even created another daemon thread to monitor this thread and shut it down from the outside, but it still fails to terminate the thread when socket read blocks.

In the try block:

TimeoutDaemon timeoutDaemon = new TimeoutDaemon(future, executorService, timeStarted);

// put a daemon on the main execution thread so it behaves
ExecutorService daemonExecutorService = Executors.newSingleThreadExecutor();
daemonExecutorService.submit(timeoutDaemon);

The TimeoutDaemon class:

private class TimeoutDaemon implements Runnable {

    private Future<?> future;
    private long timeStarted;
    private ExecutorService taskExecutorService;

    private TimeoutDaemon(Future<?> future, ExecutorService taskExecutorService, long timeStarted) {
        this.future = future;
        this.timeStarted = timeStarted;
        this.taskExecutorService = taskExecutorService;
    }

    @Override
    public void run() {
        boolean running = true;
        while (running) {
            long currentTime = System.currentTimeMillis();
            if (currentTime - timeStarted > currentTimeout + 1000) {
                running = false;
                if (!future.isDone()) {
                    String message = "Command execution is taking longer (%d ms) than the current timeout setting %d. Canceling the execution.";
                    message = String.format(message, currentTime - timeStarted, currentTimeout);
                    taskExecutorService.shutdownNow();
                }
            } else {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    log.error("Timeout Daemon interrupted. Test may be stuck. Close stuck browser windows if any.", e);
                }
            }
        }
    }
}
Max
  • 89
  • 9

2 Answers2

0

The only way I know of, is to close the sockets. You're right it's disappointing that the api doesn't allow interrupt or something. see also Interrupt/stop thread with socket I/O blocking operation

Community
  • 1
  • 1
Pelit Mamani
  • 2,321
  • 2
  • 13
  • 11
0

From the API spec:

List shutdownNow() Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution. This method does not wait for actively executing tasks to terminate. Use awaitTermination to do that.

There are no guarantees beyond best-effort attempts to stop processing actively executing tasks. For example, typical implementations will cancel via Thread.interrupt(), so any task that fails to respond to interrupts may never terminate.

You cannot interrupt Socket.read() operation.

You can take a look at the NIO package which offers InterruptibleChannel. It is possible to interrupt read and write operations on InterruptibleChannel by invoking its close method.

From the API:

If a thread is blocked in an I/O operation on an interruptible channel then another thread may invoke the channel's close method. This will cause the blocked thread to receive an AsynchronousCloseException.

John
  • 5,189
  • 2
  • 38
  • 62