Java Socket读操作在线程中没有超时

时间:2015-04-21 03:42:42

标签: java multithreading sockets selenium concurrency

我很惊讶地发现Java并发超时不会阻止线程中阻塞的套接字读取操作。

我使用Selenium RemoteWebDriver来模拟负载测试场景。我已经将{Selenium命令的执行包装在Callable中,并使用get()方法和timeout参数。当执行少于3个并发线程时,它可以正常工作,但是当有4个或更多并发线程时情况会恶化。有些线程在套接字读取时卡住,卡住的时间超过Callable的超时设置。

我在网上进行了一些研究,根本原因是Selenium代码中的硬编码套接字超时为3小时。曾经有一个黑客用反射覆盖设置但是使用最新版本我不再认为它是可以破解的。

我想知道是否有办法阻止外部IO阻塞的线程,因为我不想更改Selenium代码并最终不得不维护我自己的Selenium版本。

以下是我在代码中处理线程超时的方法:

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();
}

由于它无法随机超时,我甚至创建了另一个守护程序线程来监视此线程并从外部关闭它,但是当套接字读取阻塞时它仍然无法终止线程。

在try块中:

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);

TimeoutDaemon类:

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);
                }
            }
        }
    }
}

2 个答案:

答案 0 :(得分:0)

我所知道的唯一方法是关闭插座。 你认为api不允许中断或其他什么令你失望。 另见Interrupt/stop thread with socket I/O blocking operation

答案 1 :(得分:0)

来自API规范:

列出shutdownNow() 尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行的任务列表。 此方法不等待主动执行任务终止。使用awaitTermination来做到这一点。

除了尽力尝试停止处理主动执行任务之外,没有任何保证。例如,典型的实现将通过Thread.interrupt(),取消,因此任何无法响应中断的任务都可能永远不会终止。

您不能中断Socket.read()操作。

您可以查看提供InterruptibleChannel的NIO包。可以通过调用close方法来中断InterruptibleChannel上的读写操作。

来自API:

如果线程在可中断通道上的I / O操作中被阻塞,则另一个线程可能会调用该通道的关闭方法。这将导致被阻塞的线程收到AsynchronousCloseException。