Java 从运行时启动wkhtmltopdf.getRuntime().exec():永不终止?

Java 从运行时启动wkhtmltopdf.getRuntime().exec():永不终止?,java,process,runtime.exec,wkhtmltopdf,Java,Process,Runtime.exec,Wkhtmltopdf,我正在Java应用程序中启动wkhtmltopdf(Tomcat服务器的一部分,在64位Win7上的EclipseHelios中以调试模式运行):我想等待它完成,然后做更多的事情 String cmd[] = {"wkhtmltopdf", htmlPathIn, pdfPathOut}; Process proc = Runtime.getRuntime().exec( cmd, null ); proc.waitFor(); 但是waitFor()永远不会返回。我仍然

我正在Java应用程序中启动wkhtmltopdf(Tomcat服务器的一部分,在64位Win7上的EclipseHelios中以调试模式运行):我想等待它完成,然后做更多的事情

String cmd[] = {"wkhtmltopdf", htmlPathIn, pdfPathOut};
Process proc = Runtime.getRuntime().exec( cmd, null );

proc.waitFor();
但是
waitFor()
永远不会返回。我仍然可以在Windows任务管理器中看到该进程(通过我传递给exec()的命令行:看起来很好)。它是有效的。wkhtmltopdf生成我期望的PDF,就在我期望的地方。我可以打开它,重命名它,无论什么,甚至在进程仍在运行时(在我手动终止它之前)

在命令行中,一切正常:

c:\wrk>wkhtmltopdf C:\Temp\foo.html c:\wrk\foo.pdf Loading pages (1/6) Counting pages (2/6) Resolving links (4/6) Loading headers and footers (5/6) Printing pages (6/6) Done c:\wrk>wkhtmltopdf c:\Temp\foo.html c:\wrk\foo.pdf 加载页面(1/6) 计算页数(2/6) 解析链接(4/6) 加载页眉和页脚(5/6) 打印页面(6/6) 多恩 这个过程很好地结束了,生活还在继续

那么是什么原因导致wkhtmltopdf永远不会终止呢

我可以抓取proc.getInputStream()并查找“完成”,但这是。。。卑鄙的我想要更一般的东西

我调用了exec(),有工作目录和没有工作目录。我尝试过使用和不使用空的“env”数组。没有快乐

为什么我的进程挂起,我可以做些什么来修复它

PS:我已经在其他几个命令行应用程序中尝试过,它们都表现出相同的行为

进一步的高管困境。 我试图读出标准&错误,但没有成功。从命令行,我知道应该有一些与我的命令行体验非常相似的东西,但是当我读取proc.getInputStream()返回的输入流时,我立即得到一个EOL(-1,我使用的是
inputStream.read()

我检查了JavaDoc的进程,发现了这个

父进程使用这些流向子进程提供输入并从中获取输出。由于某些本机平台仅为标准输入和输出流提供有限的缓冲区大小,因此未能及时写入子流程的输入流或读取子流程的输出流可能会导致[b]子流程阻塞,甚至死锁[/b]

重点补充。所以我试过了。标准OutInputStream上的第一个“read()”被阻止,直到我终止该进程

使用WKHTMLTOPDF

使用通用命令行ap&no-params,因此它应该“转储使用并终止”,它吸出适当的std::out,然后终止

有趣

JVM版本问题?我使用的是1.6.023。最新的是。。。v24。我只是查看了更改日志,没有看到任何有希望的内容,但我还是会尝试更新


好的。不要让输入流填满,否则它们会阻塞。检查
.close()
也可以防止出现这种情况,但它的亮度不是很高

这在一般情况下是有效的(包括我测试过的通用命令行应用程序)

然而,具体来说,它倒了下来。wkhtmltopdf似乎正在使用一些终端操作/光标的东西来制作ASCII图形进度条。我相信这会导致inputStream立即返回EOF,而不是给我正确的值


有什么想法吗?虽然不是一个交易破坏者,但拥有它肯定会很好。

一个流程有三个流:输入、输出和错误。您可以使用单独的进程同时读取输出和错误流。请参阅和,例如。

我遇到了与您相同的问题,我解决了它。以下是我的发现:

出于某种原因,wkhtmltopdf的输出将进入进程的STDERR,而不是STDOUT。我通过从Java和perl调用wkhtmltopdf验证了这一点

例如,在java中,您必须执行以下操作:

//ProcessBuilder is the recommended way of creating processes since Java 1.5 
//Runtime.getRuntime().exec() is deprecated. Do not use. 
ProcessBuilder pb = new ProcessBuilder("wkhtmltopdf.exe", htmlFilePath, pdfFilePath);
Process process = pb.start();

BufferedReader errStreamReader = new BufferedReader(new  InputStreamReader(process.getErrorStream())); 
//not "process.getInputStream()" 
String line = errStreamReader.readLine(); 
while(line != null) 
{ 
    System.out.println(line); //or whatever else
    line = reader.readLine(); 
}
另一方面,如果您从java生成一个进程,那么必须从stdout和stderr流中读取数据(即使您什么也不做),因为否则流缓冲区将被填满,进程将挂起并且永远不会返回

为了使您的代码更具前瞻性,为了防止wkhtmltopdf的开发人员决定写入stdout,您可以将子进程的stderr重定向到stdout,并只读取一个流,如下所示:

ProcessBuilder pb = new ProcessBuilder("wkhtmltopdf.exe", htmlFilePath, pdfFilePath); 
pb.redirectErrorStream(true); 
Process process = pb.start(); 
BufferedReader inStreamReader = new BufferedReader(new  InputStreamReader(process.getInputStream())); 
实际上,我在所有需要从java生成外部进程的情况下都会这样做。这样我就不用读两条流了

如果不希望主线程阻塞,那么还应该在不同的线程中读取派生进程的流,因为从流中读取是阻塞的

希望这有帮助

更新:我在项目页面中看到了,并得到回复,这是出于设计,因为wkhtmltopdf支持在标准输出中给出实际的pdf输出。有关更多详细信息和java代码,请参见链接。

final Semaphore Semaphore=new Semaphore(numOfThreads);
    final Semaphore semaphore = new Semaphore(numOfThreads);
    final String whktmlExe = tmpwhktmlExePath;
    int doccount = 0;
    try{
        File fileObject = new File(inputDir);
        for(final File f : fileObject.listFiles()) {

            if(f.getAbsolutePath().endsWith(".html")) {
                doccount ++;
                if(doccount >500 ) {
                    LOG.info(" done with conversion of 1000 docs exiting ");
                    break;
                }
                System.out.println(" inside for before "+semaphore.availablePermits());
                semaphore.acquire();
                System.out.println(" inside for after "+semaphore.availablePermits() + " ---" +f.getName());
                new java.lang.Thread() {
                    public void run() {
                        try {
                            String F_ =  f.getName().replaceAll(".html", ".pdf") ;
                            ProcessBuilder pb = new ProcessBuilder(whktmlExe , f.getAbsolutePath(), outPutDir + F_ .replaceAll(" ", "_") );//"wkhtmltopdf.exe", htmlFilePath, pdfFilePath);
                            pb.redirectErrorStream(true);
                            Process process = pb.start();
                            BufferedReader errStreamReader = new BufferedReader(new  InputStreamReader(process.getInputStream()));  
                            String line = errStreamReader.readLine(); 
                            while(line != null) 
                            { 
                                System.err.println(line); //or whatever else
                                line = errStreamReader.readLine(); 
                            }

                            System.out.println("after completion for ");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }finally {
                            System.out.println(" in finally releasing ");
                        semaphore.release();
                        }
                  }
                }.start();
            }
        }
    }catch (Exception ex) {
        LOG.error(" *** Error in pdf generation *** ", ex);
    }

    while (semaphore.availablePermits() < numOfThreads) {//till all threads finish 
        LOG.info( " Waiting for all threads to exit "+ semaphore.availablePermits() + " --- " +( numOfThreads - semaphore.availablePermits()));
        java.lang.Thread.sleep(10000);
    }
最终字符串whktmlExe=tmpwhktmlExePath; int doccount=0; 试一试{ File fileObject=新文件(inputDir); 对于(最终文件f:fileObject.listFiles()){ if(f.getAbsolutePath().endsWith(“.html”)){ doccount++; 如果(doccount>500){ LOG.info(“已完成1000份文件的转换”); 打破 } System.out.println(“内部for before”+信号量.availablePermits()); semaphore.acquire(); System.out.println(“在”+semaphore.availablePermits()+“--”+f.getName()之后的内部for”); 新java.lang.Thread(){ 公开募捐{ 试一试{ 字符串F_u=F.getName().replaceAll(“.html”,“.pdf”); ProcessBuilder pb=新的ProcessBuilder(whktmlExe,f.getAbsolutePath(),outPutDir+f_u.replaceAll(“,”);/“wkhtmltopf.exe”,htmlFilePath,pdfFilePath); pb.重定向错误流(真); Process进程=pb.start(); BufferedReader errStreamReader=新的BufferedReader(新的InputStream