Java зависает, хотя выполнение скрипта завершено - PullRequest
7 голосов
/ 13 августа 2010

Я пытаюсь выполнить скрипт изнутри моего Java-кода, который выглядит следующим образом:

Process p = Runtime.getRuntime().exec(cmdarray, envp, dir); // cmdarray is a String array
// consisting details of the script and its arguments

final Thread err = new Thread(...); // Start reading error stream
err.start();
final Thread out = new Thread(...); // Start reading output stream
out.start();
p.waitFor();
// Close resources 

Выполнение скрипта завершено (его больше нет, pid), но Java застрял на waitFor() метод процесса !.И да, я читаю потоки вывода и ошибок в 2 отдельных потоках.Да, они объединяются в конце (после waitFor()).

Сценарий в основном устанавливает несколько RPM (например, 10 или около того) и настраивает их.Таким образом, сценарий выполняется чуть более 60 секунд.

Он выглядит примерно так:

#!/bin/sh

#exec 3>&1 >/var/log/some_log 2>&1

# If the above line is uncommented, Java recognizes that the 
# process is over and terminates fine.

tar xzf a-package-having-rpms.tar.gz
cd unpacked-folder
(sh installer-script.sh) #This installs 10 odd rpms in a subshell and configures them
cd ..
rm -rf unpacked-folder

exit 0

Достаточно шокирующе, если я добавлю следующую строку в сценарий (вверху)Java понимает, что сценарий завершен, и полностью завершает процесс.

exec 3>&1 > /var/log/some_log 2>&1

Для записи сценарий не генерирует никаких выходных данных.Ноль символов!Так что помещать в них оператор exec бессмысленно!

Но все же, как ни странно, включение оператора exec в скрипт делает работу Java !.Почему ??

Как мне избежать этого нелогичного выражения exec в скрипте ?.

Если вас интересует, как выглядит installer-script.sh, то:

#!/bin/sh

exec 3>&1 >>/var/log/another-log.log 2>&1
INSDIR=$PWD
RPMSDIR=$INSDIR/RPMS
cd $RPMSDIR
#
rpm -i java-3.7.5-1.x86_64.rpm
rpm -i --force perl-3.7.5-1.x86_64.rpm
rpm -i --nodeps mysql-libs-5.0.51a-1.vs.i386.rpm
rpm -i --nodeps mysql-5.0.51a-1.vs.i386.rpm
rpm -i --nodeps mysql-server-5.0.51a-1.vs.i386.rpm
rpm -i --nodeps perl-DBD-MySQL-3.0007-2.el5.i386.rpm
rpm -i --nodeps perl-XML-Parser-2.34-6.1.2.2.1.i386.rpm
.
.
.

Теперь, почему Java-команда в первом скрипте необходима для того, чтобы Java знала, что процесс завершен?Как я могу избежать этого exec ?, esp.поскольку первый скрипт не выдает никаких результатов.

Ожидание ответов с замиранием сердца!

Ответы [ 7 ]

7 голосов
/ 15 августа 2010

Я предполагаю, что Java не думает, что сценарий закончен, пока каналы, которые он передал ему через stdin / stdout / stderr, не будут закрыты подпроцессом. То есть в stdin больше нет активных процессов чтения, в stdout / stderr больше нет активных процессов записи.

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

Аналогично с записываемой вами трубкой, вы не получите сигнал «сломанная труба», пока последний читатель не закроет канал.

Эта проблема обычно возникает, когда ваш скрипт отключает фоновые задачи (например, недавно установленные службы), которые наследуют stdin / stdout / stderr.

Используя exec, вы явно нарушаете цепочку наследования этих каналов, чтобы фоновые процессы не использовали их.

Если в Linux, проверьте / proc / * / fd на наличие новых сервисов и посмотрите, является ли их stdin / stdout / stderr тем же каналом, который ваш Java-процесс передает вашему сценарию.

Та же самая ситуация часто возникает, когда вы запускаете сценарии /etc/init.d/xxx: они завершаются нормально, когда вы запускаете их из командной строки, но, кажется, зависают, когда вы запускаете их с какого-то монитора.

EDIT:

Вы говорите, что скрипт установщика содержит строку:

exec 3>&1 >>/var/log/another-log.log 2>&1

Первый член, 3> & 1, клонирует стандартный вывод в файл-дескриптор 3 (см. Перенаправления в man bash). Насколько я знаю, fd 3 не имеет особого значения. Затем он заменяет стандартный вывод, открывая /var/log/another-log.log, и заменяет стандартный вывод клонированием стандартного вывода. См. Раздел «Перенаправления» справочной страницы bash

Это означает, что новый дескриптор файла 3 открыт для записи в канал, который изначально был передан как STDOUT. Программы, которые ожидают быть демонами системной службы, часто закрывают файловые дескрипторы 0 (STDIN), 1 (STDOUT) и 2 (STDERR), но могут не беспокоить других. Кроме того, теперь, когда оболочка открыла FD-3, она передаст этот открытый файл любой выполняемой команде, включая фоновые команды.

Знаете ли вы, есть ли какая-то конкретная причина, по которой установщик открывает FD 3? Я предполагаю, что если вы просто удалите термин «3> & 1» из установщика, ваша проблема будет решена. Это позволит вам полностью удалить exec из вашего скрипта

1 голос
/ 15 августа 2010

Вероятной причиной является то, что при установке пакетов запускаются фоновые процессы, в которых один или оба канала (stdout / stderr) остаются открытыми.Поэтому ваши темы не прекратятся.Это означает, что эти фоновые процессы не демонизируются должным образом.Правильный демон заменяет свой исходный stdin / stdout / stderr на / dev / null или файл журнала после завершения инициализации.

0 голосов
/ 16 августа 2010

Звучит так, будто вы разбираетесь в части проблемы, но вы все еще не совсем понимаете "Большую картину".

  1. Да, можно позвонитьсценарий оболочки из Java-программы.Легко, на самом деле!<= Вы уже знаете это. </p>

  2. Да, как правило, вы должны видеть все, что напечатано на stderr или stdout.

  3. Ида, вы должны иметь возможность перенаправить stderr и / или stdout в файл.

  4. Ваша проблема, очевидно, в том, что вы не видите того, что ожидаете увидеть, КОГДА вы ожидаете увидетьЭто.Это нормально, это ожидаемо ... и, по большому счету, это ОЧЕНЬ ЖЕЛАТЕЛЬНО.

Центральный вопрос, я считаю, "буферизация ввода / вывода".

Хорошая вещь - буферизация.

Я настоятельно рекомендую вам: 1. Создать простую однопоточную тестовую программу Java для командной строки, которая вызывает сценарий оболочки.<= Вы должны увидеть «ожидаемые результаты» </p>

Разработать тестовую программу с потоками.В зависимости от структуры программы вы можете получить или не получить «ожидаемые результаты».

Заменить программу C (или другую программу Java) на сценарий оболочки <= Это делает еепроще использовать "stderr", который не имеет значения, а не "stdout". Вы также должны потерять ерунду "exec" - это была красная сельдь! </p>

Экспериментируйте и посмотрите, что вы изучаете.

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

PS: И повторяйте мою мантру: "Буферизация ввода / вывода - это хорошо"; -)

PPS:

Q: Этот вопрос был просмотрентолько 38 раз!

A: "Вы должны научиться терпению, молодой Кузнечик"; -)

Q: Это довольно критично для меня!

A: "Смирение тоже надо учить "; -)

0 голосов
/ 15 августа 2010

Вы просто не можете выполнить скрипт из Java и ждать его.Вы должны выполнить интерпретатор этого сценария.Cmdarray должен начинаться с "bash", а не с "myscript.bash".

0 голосов
/ 14 августа 2010

Я не пробовал в Linux, но столкнулся с этой проблемой в Windows.Я думаю, что мне пришлось закрыть стандарт процесса в потоке, прежде чем он завершится.

0 голосов
/ 14 августа 2010

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

0 голосов
/ 14 августа 2010

Если вы работаете в Linux / Unix, попробуйте не , использующий stdout / stderr.

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

...