В моей программе Java 11 (openjdk 11.0.3 2019-04-16 под Debian) я использую ProcessBuilder для запуска внешних команд. Внешняя команда может зависнуть, поэтому мне нужно, чтобы она истекла через определенное время.
Поэтому я использую p.waitFor(time, unit), который должен вернуться
true if the process has exited and false if the waiting time elapsed before the process has exited.
ProcessBuilder pb = new ProcessBuilder(externalCommand);
// Merges the error stream with the standard output stream
pb.redirectErrorStream(true);
Process p = pb.start();
Date start = new Date();
BufferedReader br = new BufferedReader(new InputStreamReader(
p.getInputStream()));
String tempLine;
/**
* We only want the first 10 lines otherwise it will print thousands of
* useless lines (always the same)
*/
int nbOfLinesInlogs = 10;
while ((tempLine = br.readLine()) != null) {
// We only report errors to the user
if (tempLine.toLowerCase().startsWith("error") && nbOfLinesInlogs > 0) {
Level level = Level.WARNING;
System.err.println("External command output : " + tempLine);
// There was an error
errorDetected = true;
nbOfLinesInlogs--;
}
}
if (p.waitFor(10l,
TimeUnit.NANOSECONDS)) {
long elapsedInMillis = new Date().getTime() - start.getTime();
System.err.println("External process succeeded and took " + elapsedInMillis + " ms");
// prints Externalprocess succeeded and took 15327 ms
...
}
else {
System.err.println("External process timed out");
// This is never printed!
throw new InterruptedException(
"External process timed out!");
}
Однако процесс никогда не истекает по тайм-ауту и печатает, что он занял 15 с, хотя он должен истечь через 10 нс (это просто тест, чтобы проверить, работает ли тайм-аут, как ожидалось). Я также пробовал µs, ms и s с тем же результатом.
Что я могу сделать, чтобы процесс возвращал false по истечении времени ожидания?
Любая помощь приветствуется,
@ Аарон, это что-то изменит? waitFor(...) увидит, что время превышено, и должно вернуть false, не так ли?
Обрабатываете ли вы входной поток и поток ошибок создаваемого вами Process? По моему опыту работы с ProcessBuilder, неправильная обработка потоков Process может привести к проблемному поведению. Помните, вам нужно обрабатывать потоки в отдельных потоках — один для потока ввода, а другой для потока ошибок.
@HelloWorld Аарон говорит, что если есть что-то, что занимает много времени, прежде чем вы начнете ждать, процесс на самом деле мог уже закончиться, потому что между временем, когда вы начали процесс, и временем, когда вы начали ждать, прошло 15 секунд.
Можете ли вы также напечатать время после ... непосредственно перед тем, как начать ждать?
Какую ОС (с версией) вы используете? Точная версия Java? Какой поставщик JVM? (Azuul, OpenJDK, OraceJDK и т. д.) Пробовали ли вы использовать 1 секунду (вместо наносекунд), возможно, это низкое разрешение не поддерживается вашей системой.
@Thilo: я только что сделал то, что вы предложили, и он печатает «процесс выполнен успешно и занял 0 мс». Так что с вашими советами все в порядке. Я обрабатываю потоки ввода и ошибок, и это делается до p.waitFor(...).
Я только что запустил ваш код локально. Это сработало для меня, и время истекло. (хотя я работаю на openjdk8)
@Abra: Ты прав, я с ними справляюсь. Пожалуйста, смотрите мое редактирование.
@bratkartoffel: это OpenJDK 11.0.3 под Debian Linux.
@HelloWorld: Можете ли вы попробовать другую версию openjdk? У меня сработало с openjdk8
Мне только что пришла в голову забавная идея... может быть, это твой системное время выключения? Если вы используете виртуальную машину Java и ваша система предполагают разное время, период ожидания может быть рассчитан неверно...
@TreffnonX какую «внешнюю команду» вы выполнили? Я не вижу какой-либо явной команды, упомянутой в исходном сообщении. Итак, как вы можете сказать, что это сработало для вас?
@TreffnonX Я пробовал под OpenJDK 1.8.0 с теми же результатами. Это настольный ПК, и системное время показывает правильное время. Это то, что вы имели в виду? Это команда tesseract-ocr.
@HelloWorld Я не вижу в опубликованном вами коде, где вы используете отдельный поток для чтения входного потока Process. Я что-то пропустил?
@Abra: BufferedReader br = new BufferedReader(new InputStreamReader( p.getInputStream())); но это не отдельная ветка. Так это не то, что вы имели в виду, не так ли?
@HelloWorld, да, это было. Спасибо за попытку.
@Abra, весь блок чтения предназначен только для отладки. В моем примере кода я полностью удалил его. Интересен вопрос, почему поток ожидает ожидания, несмотря на то, что истекло время ожидания.
@HelloWorld, выполните отладку через ProcessImpl.class (waitFor(long, TimeUnit)). В openjdk8 он вызывает getExitCodeProcess. Может быть, это блокирует вместо немедленного возврата...? Поскольку это нативный метод, его нельзя отлаживать, но вы можете наблюдать, возвращается ли он или блокируется.
@Abra Любая причина, почему нет? Потому что я не знал. Я только помню, что добавил этот кусок кода, потому что внешняя команда иногда бесконечно зависала. Но я думаю, что вы указали на проблему (как и другие комментаторы), поскольку удаление этого фрагмента кода решило проблему. Не могли бы вы указать мне на некоторые ресурсы, которые объясняют, в чем проблема, когда не работает в отдельном потоке?
Потому что вы использовали цикл while, который возвращает false только после закрытия ввода. Поскольку ваш ввод закрывается только после завершения процесса, ваша проверка (waitFor) происходит после завершения процесса.
@TreffnonX теперь я понимаю, что происходит и почему Абра посоветовал запускать часть «отладки» внутри отдельного потока. Не стесняйтесь публиковать свой комментарий в качестве ответа, чтобы я мог его принять (хотя я написал решение).




Спасибо всем за ваши комментарии. Согласно комментариям выше, одним из решений является удаление фрагмента кода между запуском процесса и процессом ожидания процесса (waitFor(...))
p = pb.start()
// Everything in-between removed
if (p.waitFor(10l, TimeUnit.MILLISECONDS)) {
// Now this code is not processed in case of time out
}
Причина в том, что (см. комментарий @TreffnonX)
Because I used a while loop which only returns false, once the input is closed. Since my input only closes when the process terminates, my check (waitFor) happens after the process has ended.
Другим решением может быть запуск отдельного потока, который будет запускать этот цикл while.
Нет, это не так, и это не «согласно комментариям выше». Никто этого не сказал. Это может привести к блокировке процесса на неопределенное время, пытаясь записать свой вывод, что может привести к ложному тайм-ауту, когда он работает отлично, если не считать вашего пренебрежения его вводом-выводом. Вам нужно одновременно использовать все выходные данные а также и проверить тайм-аут.
@user207421 user207421 Я не знаю, какие комментарии вы читали, но мы сделал предлагаем это. Кроме того, было предложено читать вывод в отдельном потоке, но тайм-аут может произойти без использования вывода.
@TreffnonX Я не вижу, где кто-либо предлагал не читать выходные данные процесса, но если вы это сделали, вы ошиблись, и может произойти тайм-аут потому что неиспользованного вывода, что является просто одной ошибкой, вызывающей другую.
Зачем нужно читать вывод процесса, если OP хочет только вызвать тайм-аут? Чтобы в конце концов он работал правильно, им нужно обязательно прочитать вывод, но почему это требуется для завершения процесса или истечения времени ожидания?
Я предполагаю, что за
..., который находится междуDate start = new Date();иp.waitFor(10l, TimeUnit.NANOSECONDS), не стоит 15-секундных вычислений?