Способ заставить файловый дескриптор закрыться, чтобы pclose () не блокировался?

Я создаю канал, используя popen (), и процесс вызывает сторонний инструмент, который в некоторых редких случаях мне нужно завершить.

::popen(thirdPartyCommand.c_str(), "w");

Если я просто выбрасываю исключение и раскручиваю стек, моя попытка раскрутки пытается вызвать pclose () для стороннего процесса, результаты которого мне больше не нужны. Однако pclose () никогда не возвращается, так как блокируется следующей трассировкой стека в Centos 4:

#0  0xffffe410 in __kernel_vsyscall ()
#1  0x00807dc3 in __waitpid_nocancel () from /lib/libc.so.6
#2  0x007d0abe in _IO_proc_close@@GLIBC_2.1 () from /lib/libc.so.6
#3  0x007daf38 in _IO_new_file_close_it () from /lib/libc.so.6
#4  0x007cec6e in fclose@@GLIBC_2.1 () from /lib/libc.so.6
#5  0x007d6cfd in pclose@@GLIBC_2.1 () from /lib/libc.so.6

Есть ли способ заставить вызов pclose () быть успешным перед вызовом, чтобы я мог программно избежать этой ситуации, когда мой процесс зависает, ожидая успешного завершения pclose (), когда это никогда не произойдет, потому что я прекратил вводить данные для процесс popen () и хотите выбросить его работу?

Должен ли я как-то записать конец файла в дескриптор файла ed popen (), прежде чем пытаться закрыть его?

Обратите внимание, что стороннее программное обеспечение разветвляется. В момент, когда pclose () зависла, есть четыре процесса, один из которых больше не работает:

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
abc       6870  0.0  0.0   8696   972 ?        S    04:39   0:00 sh -c /usr/local/bin/third_party /home/arg1 /home/arg2 2>&1
abc       6871  0.0  0.0  10172  4296 ?        S    04:39   0:00 /usr/local/bin/third_party /home/arg1 /home/arg2
abc       6874 99.8  0.0  10180  1604 ?        R    04:39 141:44 /usr/local/bin/third_party /home/arg1 /home/arg2
abc       6875  0.0  0.0      0     0 ?        Z    04:39   0:00 [third_party] <defunct>

1

Решение

Я вижу два решения здесь:

  • Аккуратный: ты fork(), pipe() а также execve() (или что-нибудь в exec семья, конечно …) «вручную», тогда вам решать, хотите ли вы, чтобы ваши дети стали зомби или нет. (то есть для wait() для них или нет)
  • Уродливый: если вы уверены, что в данный момент запущен только один из этих дочерних процессов, вы можете использовать sysctl() чтобы проверить, есть ли какой-либо процесс с таким именем pclose()тьфу.

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

Удачи!

РЕДАКТИРОВАТЬ:

Для вас первый вопрос: Я не знаю. Делать некоторые исследования на как найти процессы по имени, используя sysctl() должен сказать тебе то, что тебе нужно знать, я сам никогда не заходил так далеко.

На ваш второй и третий вопрос: popen() в основном это обертка для fork() + pipe() + dup2() + execl(),

fork() дублирует процесс, execl() заменяет дублированный образ процесса новым, pipe() обрабатывает межпроцессное взаимодействие и dup2() используется для перенаправления вывода … А потом pclose() будут wait() чтобы умер дублированный процесс, вот почему мы здесь.

Если вы хотите узнать больше, вы должны проверить этот ответ где я недавно объяснил, как выполнить простой форк со стандартным IPC. В этом случае это немного сложнее, так как вы должны использовать dup2() перенаправить стандартный вывод на вашу трубу.

Вы также должны взглянуть на popen()/pclose() исходники, так как они конечно с открытым исходным кодом.

Наконец, вот краткий пример, я не могу сделать это более ясным:

int    pipefd[2];

pipe(pipefd);
if (fork() == 0) // I'm the child
{
close(pipefd[0]);    // I'm not going to read from this pipe
dup2(pipefd[1], 1);  // redirect standard output to the pipe
close(pipefd[1]);    // it has been duplicated, close it as we don't need it anymore
execve()/execl()/execsomething()... // execute the program you want
}
else // I'm the parent
{
close(pipefd[1]);  // I'm not going to write to this pipe
while (read(pipefd[0], &buf, 1) > 0) // read while EOF
write(1, &buf, 1);
close(pipefd[1]);  // cleaning
}

И, как всегда, не забудьте прочитать справочные страницы и проверить все ваши возвращаемые значения.

Снова удачи!

2

Другие решения

Другое решение — убить всех своих детей. Если вы знаете, что ваши дочерние процессы — это процессы, которые запускаются, когда вы popen()тогда это достаточно просто. В противном случае вам может потребоваться дополнительная работа или использовать fork() + execve() комбо, в этом случае вы будете знать PID первого ребенка.

Всякий раз, когда вы запускаете дочерний процесс, его PPID (идентификатор родительского процесса) является вашим собственным PID. Достаточно просто прочитать список запущенных процессов и собрать те, которые имеют свои PPID = getpid(), Повторите цикл, ища процессы, у которых PPID равен одному из PID ваших детей. В конце вы создаете целое дерево дочерних процессов.

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

Процесс поэтому:

function kill_all_children()
{
std::vector<pid_t> me_and_children;

me_and_children.push_back(getpid());

bool found_child = false;
do
{
found_child = false;
std::vector<process> processes(get_processes());
for(auto p : processes)
{
// i.e. if I'm the child of any one of those processes
if(std::find(me_and_children.begin(),
me_and_children.end(),
p.ppid()))
{
kill(p.pid(), SIGSTOP);
me_and_children.push_back(p.pid());
found_child = true;
}
}
}
while(found_child);

for(auto c : me_and_children)
{
// ignore ourselves
if(c == getpid())
{
continue;
}
kill(c, SIGTERM);
kill(c, SIGCONT);  // make sure it continues now
}
}

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

void send_data(...)
{
signal(SIGALRM, handle_alarm);
f = popen("command", "w");
// do some work...
alarm(60);  // give it a minute
pclose(f);
alarm(0);   // remove alarm
}

void handle_alarm()
{
kill_all_children();
}

— о alarm(60);, местоположение зависит от вас, оно также может быть размещено до popen() если ты боишься что popen() или работа после того, как это могло также потерпеть неудачу (то есть у меня были проблемы, когда труба заполняется, и я даже не достигаю pclose() потому что тогда дочерний процесс зацикливается навсегда.)

Обратите внимание, что alarm() возможно, не самая лучшая идея в мире. Вы можете предпочесть использовать нить со сном из poll() или же select() на FD, который вы можете проснуться по мере необходимости. Таким образом, поток будет вызывать kill_all_children() после сна, но вы можете отправить ему сообщение, чтобы он проснулся рано и сообщил, что pclose() случилось, как и ожидалось.

Примечание: я оставил реализацию get_processes() из этого ответа. Вы можете прочитать это из /proc или с libprocps библиотека. я имею такая реализация в моем проект Snapwebsites. Это называется process_list, Вы можете просто пожинать этот класс.

1

По вопросам рекламы [email protected]