LINUX.ORG.RU

Поругайте «стража» дочерних процессов

 ,


0

2

При реализации видеостримера (туда еще нужно будет обработку добавить, но пока основная функция — собирать кадры видео и выплевывать в браузер) на некоторых USB-камерах ffmpeg при длительной работе выпадает в assert (вообще, дикость!). Аналогичные проблемы бывают при использовании libwebsockets, которая в случае отключения клиента тоже выбрасывает assert.

Чтобы бороться с этим я решил делать форк в самом начале, а потом контролировать работу дочернего процесса и если помрет — перезапускать заново.

Вот что получилось:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

int child_killed = 0;

void sighndlr(int sig){
	printf("got signal %d\n", sig);
	if(sig == SIGCHLD){
		printf("Child was killed\n");
		child_killed = 1;
	}else{
		printf("suicide\n");
		exit(sig);
	}
}

pid_t create_child(){
	child_killed = 0;
	int i;
	pid_t p = fork();
	if(p == 0){
		printf("child\n");
		char *ptr = malloc(10000);
		for(i = 0; i < 256; ++i, ptr += 1024){
			*ptr += i;
		}
	}
	return p;
}

int main(){
	int i;
	for(i = 0; i < 255; ++i)
		signal(i, sighndlr);
	for(i = 0; i < 10; ++i){
		pid_t p = create_child();
		if(p){
			printf("child #%d with PID %d\n", i, p);
			while(!child_killed)
				usleep(10000);
		}else return 0;
	}
	return 0;
}


Фактически функция create_child() в дочернем процессе будет запускать "падучие функции", а родительский только будет "пасти" дочерние.

Вопрос: есть ли какие-то недостатки у такой реализации?

☆☆☆☆☆

Ответ на: комментарий от arturpub

Спасибо!

Так и знал, что можно проще сделать!!!

Eddy_Em ☆☆☆☆☆ ()

Мыши плакали, кололись, но продолжали есть кактус. Недостаток один: велосипедизм.

intelfx ★★★★★ ()
Последнее исправление: intelfx (всего исправлений: 1)
Ответ на: комментарий от arturpub

Вот такая минимальная версия с wait получилась:

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>

pid_t create_child(){
	int i;
	pid_t p = fork();
	if(p == 0){
		printf("child ");
		fflush(stdout);
		char *ptr = malloc(10000);
		for(i = 0; i < 256; ++i, ptr += 1024){
			*ptr += i;
		}
	}
	return p;
}

int main(){
	int i;
	for(i = 0; i < 10; ++i){
		pid_t p = create_child();
		if(p){
			printf("child #%d with PID %d\n", i, p);
			wait(NULL);
			printf("died\n");
		}else return 0;
	}
	return 0;
}

А я, к стыду своему, про wait как-то и забыл...

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от intelfx

Все, исправился: заменил ручной отлов сигналов на wait().

После обеда обновлю код и буду проверять на падучесть. А то железо уже больше месяца лежит без движения, надо заканчивать работу.

Eddy_Em ☆☆☆☆☆ ()

«child_killed» flag should be a «volatile». а также его инкременты, установки и сбросы должны быть атомик. а то потом компилер может такого наооптимизировать, что будешь слово «вечность» складывать из льдышек

MKuznetsov ★★★★★ ()
Ответ на: комментарий от Eddy_Em

По-моему тебе намекали, что для управления демонами нужен systemd :)

goingUp ★★★★★ ()
Ответ на: комментарий от Eddy_Em

Честно говоря, я его за всю жизнь так ни разу и не вызвал (кроме как в тестовых хелошках). SIG_IGN во все поля. А вот тебе повезло :)

arturpub ★★ ()
Последнее исправление: arturpub (всего исправлений: 1)
Ответ на: комментарий от arturpub

Это же не треды, зачем валатайл?

там сигналы, которые приходят когда захотят.

MKuznetsov ★★★★★ ()
Ответ на: комментарий от MKuznetsov

«child_killed» flag should be a «volatile».

А также sig_atomic_t.

anonymous ()
Ответ на: комментарий от Eddy_Em

Кстати я тут подумал, что неплохо бы по сигналу в паренте вручную килять чилда. Иначе админ враппер по пиду прибьет, а сервис так и останется.

arturpub ★★ ()
Ответ на: комментарий от goingUp

По-моему тебе намекали, что для управления демонами нужен systemd :)

по старинке такой «самоумирающий по сегфолту» сервис прописывали в inittab и не мучались

MKuznetsov ★★★★★ ()

Есть. Respawn в иниттаб смотрелся бы куда проще.

ioway ()
Ответ на: комментарий от arturpub

В случае сегфолта SIG_IGN низзя. Как и в случае с дурацкими ассертами.

Вот интересно: нафига в «продакшновых» библиотеках ассерты понаоставляли?

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от MKuznetsov

Я не хочу в inittab с respawn писать. Ну его нафиг!

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от Eddy_Em

Вот интересно: нафига в «продакшновых» библиотеках ассерты понаоставляли?

А как еще узнать, что он таки стреляет? ;)

arturpub ★★ ()
Ответ на: комментарий от arturpub

Во:

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>

volatile pid_t childpid = 0;

void signal_handler(int sig){
	printf("PID %zd got signal %d, die\n", getpid(), sig);
	if(childpid)
		kill(childpid, SIGTERM);
	exit(sig);
}

pid_t create_child(){
	int i;
	pid_t p = fork();
	if(p == 0){
		printf("child\n");
		sleep(1);
	}
	return p;
}

int main(){
	int i = 0;
	signal(SIGTERM, signal_handler);  // kill (-15)
	signal(SIGINT, signal_handler);   // ctrl+C
	signal(SIGQUIT, signal_handler);  // ctrl+\  .
	signal(SIGTSTP, SIG_IGN);         // ctrl+Z
	signal(SIGSEGV, signal_handler);
	printf("my PID: %zd\n", getpid());
	while(1){
		childpid = create_child();
		if(childpid){
			printf("child #%d with PID %d\n", ++i, childpid);
			wait(NULL);
			printf("died\n");
		}else return 0;
	}
	return 0;
}

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от Eddy_Em

Ох уж эти нубы... Почитай man prctl, откроешь для себя замечательную вещь: PR_SET_PDEATHSIG. Для умирания child'ов при смерти родителя всего-то надо сразу же после fork в дочернем процессе

prctl(PR_SET_PDEATHSIG, SIGTERM);

Еще тебе вообще стоит почитать man 7 signal, где прямым текстом говорится

The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.

Вообще тут уже упоминали systemd? Леннарт хоть и пишет монстров, но хотя бы мануалы читает в отличие от таких вот «домашних умельцев».

kawaii_neko ★★★ ()
Последнее исправление: kawaii_neko (всего исправлений: 1)
Ответ на: комментарий от kawaii_neko

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

arturpub ★★ ()
Ответ на: комментарий от kawaii_neko

Почитай man prctl

Какая-то загадачная штука. Но попробую с ней.

Еще тебе вообще стоит почитать man 7 signal, где прямым текстом говорится

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

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от kawaii_neko

Спасибо!

У, клево! При убиении «папаши» по -9 дочерний мрет спокойненько...

Глядишь, эдак из меня всем ЛОРом погромиста сделаете ☺

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от arturpub

Для гарантированного умирания чилдов нужно открывать к ним пайп

Правда? Ну и как же ты будешь гарантировать умирание exec'нутого child'а при закрытии пайпа? Давай расскажи, а я посмеюсь.

непортабельную анестезию

Дуй обратно на свой bsd.org.ru или откуда там ты нарисовался.

kawaii_neko ★★★ ()
Ответ на: комментарий от Eddy_Em

А где ты у меня видишь попытки перехвата этих сигналов?

А это по-твоему что?

signal(SIGTSTP, SIG_IGN);

kawaii_neko ★★★ ()
Ответ на: комментарий от Eddy_Em

signal(SIGSEGV,

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

если дочерний процесс один, то вместо wait лучше waitpid

если есть wait или waitpid то надо устраивать полный «разбор полётов» по их результатам

если используете сигналы, то должны управлять и масками сигналов

если высылаете дочернему процессу SIGTERM, то неплохо «хоронить с почестями», то есть хоть чуток подождать до SIGCHLD или сделать «контрольный в голову» SIGKILL

ps/ к концу треда и на Nn-й версии «стража» вы всё-же поймёте, что respawn в inittab это не такое-уж и зло

MKuznetsov ★★★★★ ()
Ответ на: комментарий от arturpub

Я, собственно, ни на секунду не сомневался, что тебе нечего мне рассказать.

kawaii_neko ★★★ ()
Ответ на: комментарий от Eddy_Em

KISS ;)

#include <sys/types.h>
#include <sys/wait.h>
#include <err.h>
#include <unistd.h>

int
go(char **argv)
{
	int status = 0;
	pid_t pid;

	switch ((pid = fork())) {
	case -1:
		errx(1, "fork failed");
		/* NOTREACHED */
	case 0:	
		execvp(*argv, argv);
		errx(1, "execution of %s failed", *argv);
		/* NOTREACHED */
	default:
		waitpid(pid, &status, 0);
		/* FALLTHROUGH */
	}

	return WEXITSTATUS(status);
}

int
main(int argc, char **argv)
{
	int status, n = 100;

	if (!*++argv)
		errx(1, "no args");

	while ((status = go(argv)) && n--)
		warnx("child died, exit status %d, refork", status);

	return 0;
}
beastie ★★★★★ ()
Ответ на: комментарий от arturpub

А как еще узнать, что он таки стреляет? ;)

Может тестами все покрыть и запускать их когда надо и где над? assert это только для документирования кода и для срабатывания в тестах.

prefetch ()

Работает, шельма!

...
Assertion atomic_int_get_gcc(&s->buffers_queued) >= 1 failed at libavdevice/v4l2.c:516
Child 6416 died
main (/home/eddy/Docs/SAO/Raspberry/astrovideoguide_v2/main.c, line 341): Created child with PID 19226
...
Eddy_Em ☆☆☆☆☆ ()

Почему юникод в кои8 не перегоняет?

Deleted ()

выпадает в assert (вообще, дикость!)

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

anonymous ()
Ответ на: комментарий от anonymous

когда у меня ffmpeg валился на коррапченом файле — я отослал им сэмпл и они быстро пофиксили

anonymous ()
Ответ на: комментарий от anonymous

Этот баг висит уже давно в багтрекере. Я ж сразу гуглил. Не хотят фиксить.

Eddy_Em ☆☆☆☆☆ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.