Запуск wkhtmltopdf из Runtime.getRuntime().exec(): никогда не завершается?

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

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].

Добавлен акцент. Так что я попробовал это. Первый 'read()' на входном потоке Standard Out заблокирован, пока я не уничтожил процесс...

С WKHTMLTOPDF

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

Интересный!

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


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

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

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

Любые идеи? Вряд ли это нарушит сделку, но это определенно было бы приятно иметь.


person Mark Storer    schedule 31.03.2011    source источник
comment
Спасибо за быстрые ответы, ребята. Это довольно хорошо решает общий случай, но есть некоторые вещи, специфичные для wkhtmltopdf. Я обновлю вопрос.   -  person Mark Storer    schedule 01.04.2011


Ответы (3)


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

По какой-то причине вывод 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 решат писать в стандартный вывод, вы можете перенаправить стандартный поток дочернего процесса на стандартный вывод и прочитать только один поток следующим образом:

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-файла в STDOUT. Пожалуйста, смотрите ссылку для получения более подробной информации и кода Java.

person Riyas Valiya    schedule 16.01.2012
comment
Очень полезно. Большое спасибо за публикацию решения. - person user10053673; 30.03.2021

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

person MByD    schedule 31.03.2011

Вы должны читать потоки в другом нить.

person Thomas Mueller    schedule 31.03.2011
comment
В качестве дополнительной опции вы можете захватить потоки и просто закрыть их. Не самая лучшая идея в мире, и «Мне все равно», но в крайнем случае это сработает. - person Mark Storer; 01.04.2011