首页 > 解决方案 > Guava SimpleTimeLimiter 不会从 Process.getInputStream 超时 BufferedReader.readLine

问题描述

问题

我正在尝试围绕通话包装某种超时BufferedReader.readLine()。BufferedReader 是从java.lang.Process.getInputStream(). 到目前为止我发现的最有希望的事情是如何在 Java 1.4 中设置 BufferedReader 和 PrintWriter 的超时时间?看看 Guava 的SimpleTimeLimiter揭示,他们似乎解决了这个问题,就像我自己会做的那样:通过使用 Java 7 的 Executors/Callables/Futures。然而,这似乎不起作用,因为超时根本不会发生在我的 Unix 环境中。至少不在可接受的范围内,可能永远不会。

编码

@Override
@Transactional
public Process call()
{
    java.lang.Process systemProcess = null;
    try
    {
        ProcessCallable.LOGGER.debug("executing command: {} ||| timeout: {} {}", this.process.getCommand(), this.getTimeout(), this.getTimeoutUnit());
        final String[] args = CommandLineUtils.translateCommandline(this.process.getCommand());
        final ProcessBuilder processBuilder = new ProcessBuilder(args);
        processBuilder.redirectErrorStream(true);
        systemProcess = processBuilder.start();
        final int pid = this.processService.getPid(systemProcess);
        try (final BufferedReader reader = new BufferedReader(new InputStreamReader(systemProcess.getInputStream()));
            final OutputStream os = systemProcess.getOutputStream())
        {
            ProcessCallable.LOGGER.debug("PID: {}", pid);
            String line = this.timeLimiter.callWithTimeout(reader::readLine, this.getTimeout(), this.getTimeoutUnit(), true);
            while (line != null)
            {
                ProcessCallable.LOGGER.debug("line: \"{}\"", line);
                line = this.timeLimiter.callWithTimeout(reader::readLine, this.getTimeout(), this.getTimeoutUnit(), true);
            }
        }
        final int exitCode = systemProcess.waitFor();
        ProcessCallable.LOGGER.debug("exit code for PID: {} = {}", pid, exitCode);
    }
    catch (final Exception ex)
    {
        ProcessCallable.LOGGER.error("error while executing command: " + this.process.getCommand(), ex);
        this.processService.killTree(systemProcess);
    }

    ...
}

日志

[2018-05-06 07:06:32] [DEBUG] [?.process.ProcessCallable] [processRunner1] executing command: /etc/init.d/starbound update ||| timeout: 1 MINUTES
[2018-05-06 07:06:32] [DEBUG] [?.service.ProcessService] [processRunner1] getting PID of java.lang.UNIXProcess
[2018-05-06 07:06:32] [DEBUG] [?.process.ProcessCallable] [processRunner1] PID: 15258
[2018-05-06 07:06:32] [DEBUG] [?.process.ProcessCallable] [processRunner1] line: "Updating Starbound Daemon"
[2018-05-06 07:06:34] [DEBUG] [?.process.ProcessCallable] [processRunner1] line: "Redirecting stderr to '/home/steam/logs/stderr.txt'"
[2018-05-06 07:06:34] [DEBUG] [?.process.ProcessCallable] [processRunner1] line: "Looks like steam didn't shutdown cleanly, scheduling immediate update check"
[2018-05-06 07:06:34] [DEBUG] [?.process.ProcessCallable] [processRunner1] line: "[  0%] Checking for available updates..."
[2018-05-06 07:06:34] [DEBUG] [?.process.ProcessCallable] [processRunner1] line: "[----] Verifying installation..."
[2018-05-06 07:06:34] [DEBUG] [?.process.ProcessCallable] [processRunner1] line: "Steam Console Client (c) Valve Corporation"
[2018-05-06 07:06:34] [DEBUG] [?.process.ProcessCallable] [processRunner1] line: "-- type 'quit' to exit --"
[2018-05-06 07:06:34] [DEBUG] [?.process.ProcessCallable] [processRunner1] line: "Loading Steam API...OK."
[2018-05-06 07:06:34] [DEBUG] [?.process.ProcessCallable] [processRunner1] line: ""
[2018-05-06 07:11:22] [ERROR] [?.process.ProcessCallable] [processRunner1] error while executing command: /etc/init.d/starbound update
com.google.common.util.concurrent.UncheckedTimeoutException: java.util.concurrent.TimeoutException
        at com.google.common.util.concurrent.SimpleTimeLimiter.callWithTimeout(SimpleTimeLimiter.java:143)
        at ?.process.ProcessCallable.call(ProcessCallable.java:84)
        at ?.process.ProcessCallable.call(ProcessCallable.java:32)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
        at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
        at com.sun.proxy.$Proxy544.call(Unknown Source)
        at org.springframework.security.concurrent.DelegatingSecurityContextCallable.call(DelegatingSecurityContextCallable.java:86)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)
Caused by: java.util.concurrent.TimeoutException
        at java.util.concurrent.FutureTask.get(FutureTask.java:205)
        at com.google.common.util.concurrent.SimpleTimeLimiter.callWithTimeout(SimpleTimeLimiter.java:130)
    ... 20 more

我实际上确实需要每行而不是每个进程超时。总体执行时间变化很大,我试图通过不时要求至少一些输出来确定进程是否已冻结。从日志中可以看出,在我最终决定从控制台终止进程之前,我已经等待了大约 5 分钟(5 * 超时),以便 Java/SimpleTimeLimiter 检测到超时。杀死进程后,TimeoutException实际上会被抛出。我很难理解为什么future.get(timeoutDuration, timeoutUnit)无法正确超时阻塞 BufferedReader.readLine() 调用。我很确定我已经成功地使用future.get()了超时和Input-/OutputStreams过去(在工作中,而我什至不知道TimeLimiter)。那么这里的问题是什么?是底层Process吗?

标签: javaspringprocesstimeoutbufferedreader

解决方案


这不起作用,因为 Guava 的 SimpletimeLimiter 只中断被调用的方法,请参阅:

https://github.com/google/guava/blob/master/guava/src/com/google/common/util/concurrent/SimpleTimeLimiter.java

在这种情况下,Guava 代码将尝试中断 Reader.readLine() 但该 java IO 方法不支持中断。因此,您可以尝试尽可能多地中断它,该代码只是不检查 Thread 的 isInterrupted 布尔标志,因此 Guava 调用无效。您可能想研究 Java 的 NIO(非阻塞 IO)。或者,可以不使用 Reader.readLine(),而是逐个字符地读取并在读取之前调用 isReady()(永远不会陷入调用 read() 但没有什么可读取的情况),并且,检查是否您的线程已被中断(并使用番石榴,为什么不)。


推荐阅读