Запуск wkhtmltopdf из Runtime.getRuntime (). Exec (): никогда не завершается? - PullRequest
8 голосов
/ 01 апреля 2011

Я запускаю wkhtmltopdf из моего Java-приложения (часть сервера Tomcat, работающего в режиме отладки в Eclipse Helios на 64-битной Win7): я хотел бы дождаться его завершения, затем Do More Stuff.

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

Процесс завершается очень хорошо, и жизнь продолжается.

Так что же такое runtime.exec()в результате чего wkhtmltopdf никогда не завершится?

Я мог бы взять proc.getInputStream () и поискать "Done", но это ... подло.Я хочу что-то более общее.

Я вызывал exec () с рабочим каталогом и без него.Я пробовал с и без пустого массива "env".Никакой радости.

Почему мой процесс зависает, и что я могу сделать, чтобы это исправить?

PS: я пробовал это с парой других приложений командной строки, и они оба показываюттакое же поведение

Дальнейшие проблемы с исполнением.

Я пытаюсь прочитать стандартную ошибку и ошибку, но безуспешно.Из командной строки я знаю, что должно быть что-то удивительно похожее на мой опыт работы с командной строкой, но когда я читаю поток ввода, возвращаемый proc.getInputStream (), я сразу получаю EOL (-1, я использую inputStream.read()).

Я проверил JavaDoc для процесса и обнаружил это

Родительский процесс использует эти потоки для подачи входных данных и получения выходных данных из подпроцесса.Поскольку некоторые собственные платформы предоставляют ограниченный размер буфера только для стандартных входных и выходных потоков, невозможность оперативной записи входного потока или чтения выходного потока подпроцесса может привести к блокировке подпроцесса [b] и даже к тупиковой блокировке [/ b].

Акцент добавлен.Я попробовал это.Первое чтение () в Standard Out inputStream блокировалось до тех пор, пока я не завершил процесс ...

WITH WKHTMLTOPDF

С общей командной строкой ap & no paramsпоэтому он должен «сбросить использование и прекратить», он высасывает соответствующий std :: out, а затем завершает.

Интересно!

Проблема с версией JVM?Я использую 1.6.0_23.Последняя версия ... v24.Я только что проверил журнал изменений и не вижу ничего многообещающего, но все равно попробую обновить.


Хорошо.Не позволяйте входным потокам заполняться, иначе они будут блокироваться.Проверьте..close() также может предотвратить это, но не очень ярко.

Это работает в целом (включая общие приложения командной строки, которые я тестировал).

В частности однако, оно падает.Похоже, что wkhtmltopdf использует некоторые манипуляции с терминалом / курсор для создания ASCII-графического индикатора выполнения.Я считаю, что это заставляет inputStream немедленно возвращать EOF, а не давать мне правильные значения.

Есть идеи?Едва ли это соглашение, но определенно было бы приятно иметь.

Ответы [ 4 ]

10 голосов
/ 16 января 2012

У меня была та же проблема, что и у вас, и я решил ее.Вот мои выводы:

По некоторым причинам выходные данные из wkhtmltopdf отправляются в STDERR процесса, а НЕ в STDOUT.Я подтвердил это, вызвав wkhtmltopdf из Java, а также из perl

. Так, например, в 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.Таким образом, мне не нужно читать два потока.

Вам также следует читать потоки порожденного процесса в разных потоках, если вы не хотите, чтобы ваш основной поток блокировался, поскольку чтение из потоков блокирует.

Надеюсь, это поможет.

ОБНОВЛЕНИЕ : I поднял эту проблему на странице проекта и ответил, что это сделано намеренно, потому что wkhtmltopdf поддерживает выдачу фактического вывода PDF в STDOUT.Пожалуйста, смотрите ссылку для более подробной информации и Java-код.

4 голосов
/ 01 апреля 2011

Процесс имеет 3 потока: вход, выход и ошибка.Вы можете одновременно читать и вывод, и поток ошибок, используя отдельные процессы.см. этот вопрос и его принятый ответ и также этот например.

2 голосов
/ 02 ноября 2012
    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);
    }
2 голосов
/ 01 апреля 2011
...