2013-11-18 2 views
5

По некоторым причинам, не имеющим отношения к этому вопросу, я запускаю Java-сервер в сценарии bash не напрямую, а через подстановку команд под отдельной суб-оболочкой и в фоновом режиме. Цель заключается в том, что подкоманда возвращает идентификатор процесса сервера Java в качестве стандартного вывода. Fragement в вопросе заключается в следующем:Bash Command Substitution Предоставление Weird Несоответствующий результат

launch_daemon() 
{ 
    /bin/bash <<EOF 
    $JAVA_HOME/bin/java $JAVA_OPTS -jar $JAR_FILE daemon $PWD/config/cl.yml <&- & 
    pid=\$! 
    echo \${pid} > $PID_FILE 
    echo \${pid} 
EOF 
} 

daemon_pid=$(launch_daemon) 

echo ${daemon_pid} > check.out 

В Java демон в вопросительных гравюр к стандартной ошибке и завершает работу, если есть проблема в инициализации, в противном случае она закрывает стандартный, и стандартный ERR и продолжает свой путь. Позже в скрипте (не показан) я делаю чек, чтобы убедиться, что процесс сервера запущен. Теперь о проблеме.

Всякий раз, когда я проверяю $ PID_FILE выше, он содержит правильный идентификатор процесса в одной строке.

Но когда я проверяю файл check.out, иногда содержит правильный идентификатор, в других случаях он содержит идентификатор процесса повторяется дважды на одной и той же линии, разделенных пробелом charcater как в:

34056 34056 

I я использую переменную $ daemon_pid в сценарии выше в сценарии, чтобы проверить, работает ли сервер, поэтому, если он содержит pid, который повторяется дважды, это полностью исключает тест, и он неправильно считает, что сервер не запущен. Скрипт с скриптом на моем сервере, на котором запущена CentOS Linux, добавив больше эхо-заявлений и т. Д., Похоже, перевернул поведение обратно к правильному одному из $ daemon_pid, содержащему идентификатор процесса только один раз, но если я думаю, что он исправил его и проверил этот сценарий для моего исходного кода репо, а также выполнить сборку и развертывание, я начинаю видеть такое же плохое поведение.

Сейчас я установил это, предполагая, что $ daemon_pid может быть плохим и пропуская его через AWK следующим образом:

mypid=$(echo ${daemon_pid} | awk '{ gsub(" +.*",""); print $0 }') 

Тогда $ mypid всегда содержит правильный идентификатор процесса, и все это хорошо, но само собой скажем, я хотел бы понять, почему он ведет себя так, как он. И прежде чем вы спросите, я посмотрел и посмотрел, но сервер Java, о котором идет речь, НЕ распечатывает свой идентификатор процесса до его стандартного номера перед закрытием стандартного.

Действительно оценен экспертный ввод.

+0

Как вы защищаете от одновременных прогонов? Возможно, вы тоже используете скрипт? –

+0

Хороший вопрос, на самом деле он не защищен от этого. Сборка и развертывание осуществляется через нашу собственную коробку Jenkins, и сервер является нашим, поэтому мы контролируем, поэтому я уверен, что этого не происходит. Плюс я видел такое же поведение, хотя не всегда и не последовательно, при запуске скрипта start.sh, который содержит вышеупомянутый фрагмент, непосредственно из командной строки в развернутом ящике. – user2456600

+0

Кроме того, мы не запускаем выскочку в качестве стандартного демона. У нас есть незанятый процесс, основанный на использовании playbook для развертывания и запуска, который запускает только скрипт start.sh. На предыдущем шаге в незанятой пьесе она убивает любого существующего работающего демона. – user2456600

ответ

4

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

TL; DR: Вы можете исправить эту проблему, удалив <&- из сценария.

Закрытие stdin в лучшем случае сомнительны, а не только по той причине, упомянутой @JonathanLeffler («Программа имеет право на стандартный ввод, это открыто.»), Но, что более важно, потому что stdin используется сам bash процесса и закрытие его в фоновом режиме вызывает состояние гонки.

Для того, чтобы увидеть, что происходит, рассмотрим следующий довольно странный сценарий, который можно назвать устройством Bash Duff, за исключением того, что я не уверен, что даже Дафф одобрил бы: (также, как представлено, это не так полезно Но кто-то где-то использовал его в каком-то хаке. Или, если нет, теперь они увидят это.)

/bin/bash <<EOF 
if (($1<8)); then head -n-$1 > /dev/null; fi 
echo eight 
echo seven 
echo six 
echo five 
echo four 
echo three 
echo two 
echo one 
EOF 

Для этого, чтобы работать, bash и head оба должны быть готовы разделить stdin, в том числе разделяя позицию файла. Это означает, что bash должен убедиться, что он очищает буфер чтения (или не буфер), а head должен убедиться, что он обращается к концу той части ввода, которую он использует.

(хак работает только потому, что bash обрабатывает здесь-документы путем копирования их во временный файл. Если используется труба, это не было бы возможно head искать в обратном направлении.)

Теперь, что бы если head бежал в фоновом режиме? Ответ заключается в том, что «практически все возможно», потому что bash и head участвуют в гонке для чтения из того же дескриптора файла. Запуск head в фоновом режиме был бы очень плохой идеей, даже хуже, чем оригинальный хак, который, по крайней мере, предсказуем.

Теперь давайте вернемся к реальной программе под рукой, упрощенно его первой необходимости:

/bin/bash <<EOF 
cmd <&- & 
echo \$! 
EOF 

Линия 2 этой программы (cmd <&- &) вилок прочь отдельный процесс (для запуска в фоновом режиме). В этом процессе он закрывает stdin, а затем вызывает cmd.

Между тем, на переднем плане процесс продолжает чтение команды из stdin (его stdin FD не был закрыт, так что это нормально), что заставляет его выполнить команду echo.

Теперь вот затирание: bash знает, что ему нужно поделиться stdin, поэтому он не может просто закрыть stdin. Он должен убедиться, что позиция файла stdin указывает на нужное место, даже если оно, возможно, действительно прочитало значение ввода буфера. Таким образом, перед тем, как он закрывается stdin, он обращается назад в конец текущей командной строки. [1]

Если этот поиск происходит до того, как передняя панель bash выполнит echo, тогда проблем нет. И если это произойдет после того, как bash на первом плане будет сделан с этим документом, также нет проблем. Но что, если это произойдет , пока эхо работает? В этом случае после завершения echobash перечитает команду echo, потому что stdin был перемотан, а echo будет выполнен снова.

И это именно то, что происходит в ОП. Иногда поиск по фону завершается в самое неподходящее время и вызывает выполнение echo \${pid} дважды. Фактически, это также вызывает выполнение echo \${pid} > $PID_FILE дважды, но эта строка является идемпотентной; если бы это было echo \${pid} >> $PID_FILE, двойное выполнение было бы видимым.

Итак, решение прост: удалите <&- со строки запуска сервера и, при необходимости, замените его на </dev/null, если вы хотите, чтобы сервер не мог читать с stdin.


Примечания:

Примечание 1: Для тех, кто больше знакомы с исходным Баш кода и его ожидаемое поведение, чем я, я считаю, что искать и близко происходит в конце case r_close_this: в функции do_redirection_internal в redir.c, примерно в линии +1093:

check_bash_input (redirector); 
close_buffered_fd (redirector); 

Первый вызов делает lseek, а второй делает close. Я видел поведение, используя strace -f, а затем искал код для правдоподобного вида lseek, но я не стал беспокоиться о проверке в отладчике.

+0

Вау, по какой-то причине я перешел после моего обходного пути, но никогда не был уведомлен об этом ответе. Это наиболее полно, спасибо за рытье настолько глубоко :-) – user2456600

+0

Теперь, когда я возвращаюсь через некоторое время, чтобы проверить мой вопрос, поднял еще один полезный совет по упрощению моего awk-взлома. Я стыжусь своей головой. – user2456600

Смежные вопросы