threads: child process nach x minuten automatisch beenden
-
hi
ich verwende fork und anschließend execl im child um ein externes binary zu starten. das funktioniert auch alles zur vollen zufriedenheit nur manchmal hat das externe program schwierigkeiten mit den eingangsparametern und rechnet stundenlang herum ohne zu einer lösung zu kommen (wobei es im normalfall etwa 2 sekunden bis maximal 5 minuten dauert) in diesem fall möchte ich gerne nach zb 10 minuten das externe program beenden und in meinem aufrufenden program einfach einen fehlercode zurückgeben und danach ganz normal fortfahren. bisher siehts so aus bei mir
pid_t CProcess::popen2(FILE* files[], char const* command) /* * method emulates the popen via pipe and execl, stdout and stderr are caught */ { static const int PIPE_READ = 0; static const int PIPE_WRITE = 1; int outfd[2], errfd[2]; pid_t pid; if (pipe(outfd) == -1 || pipe(errfd) == -1) { LOGGER.log("CProcess::popen2", "could not open pipes", WARNING); return -1; } pid = fork(); if (pid == -1) { LOGGER.log("CProcess::popen2", "could not fork", WARNING); return -1; } if (pid == 0) { /* child process */ /* no errors are reported to the caller from here on */ dup2(outfd[PIPE_WRITE], STDOUT_FILENO); /* redireceting stdout */ dup2(errfd[PIPE_WRITE], STDERR_FILENO); /* redireceting stderr */ close(outfd[PIPE_READ]); /* close reading end */ close(errfd[PIPE_READ]); execl("/bin/sh", "sh", "-c", command, NULL); /* shell call */ LOGGER.log("CProcess::popen2", "execl failed", WARNING); exit(0); /* if excel fails end it */ } // // hier sollte ein stück code stehen dass nach bestimmtem timeout den son beendet // close(outfd[PIPE_WRITE]); /* close writing ends */ close(errfd[PIPE_WRITE]); files[0] = fdopen(outfd[PIPE_READ], "r"); /* return reading end to the caller */ files[1] = fdopen(errfd[PIPE_READ], "r"); if (files[0] == NULL || files[1] == NULL) { LOGGER.log("CProcess::popen2", "fdopen failed", ERROR); } return pid; }
wie geh ich da korrekterweise vor? den vater schlafen lassen und dann nach 10 min den vater mit kill -9 nach dem externen program hauen zu lassen scheint mir doch etwas brutal. zumal der vater dann ja immer 10 min schläft, also müsste man ihn zb alle 5 sec wecken und überprüfen obs den externen noch gibt ...
... wie würde da eine ellegante lösung aussehen?
thx
lordy
-
deine methode ist schon ganz gut. sobald ein kind beendet wird, wird dem elternprozess SIGCHLD geschickt. dieses kannst du abfangen und erkennst damit, dass das kind tot ist und du auch den elternprozess beenden kannst. du könntest statt SIGKILL auch SIGTERM verwenden. sollte aber das ausgeführte programm für SIGTERM einen handler installieren der das signal ignoriert, musst du SIGKILL verwenden.
-
namenlos schrieb:
deine methode ist schon ganz gut. sobald ein kind beendet wird, wird dem elternprozess SIGCHLD geschickt. dieses kannst du abfangen und erkennst damit, dass das kind tot ist und du auch den elternprozess beenden kannst. du könntest statt SIGKILL auch SIGTERM verwenden. sollte aber das ausgeführte programm für SIGTERM einen handler installieren der das signal ignoriert, musst du SIGKILL verwenden.
ok thx aber wie mach ich das genau, kannst du das codetechnisch etwas anskizzieren?
etwa so schauts aus bisher, speziell die beiden kommentare mit somehow müssten durch code ersetzt werdenif (pid == 0) { /* child process */ ... } else { /* parent */ int waitIter=0; bool sonFinished = false; while (waitIter<MAX_PROCESS_TIME && !sonFinished) { // wait 1 hour and check every 1 minute Sleep(FATHER_WAKEUP_STEP); /* somehow check SIGCHLD if son has finished set sonFinished to true*/ waitIter+=FATHER_WAKEUP_STEP; } if (!sonFinished) { /* somehow kill son */ } }
-
SIGCHLD ist ein signal, das dem elternprozess beim tot des kindprozesses geschickt wird. du musst das einfach mit einem signal handler abfangen.
der signal handler muss wait() aufrufen, um den statuscode des kindprozesses aufzunehmen. du kannst dann auch gleich im handler exit aufrufen und den elternprozess beenden.
sleep rufst du mit 3600 auf. sobald SIGCHLD ankommt, wird sleep unterbrochen und der signal handler aufgerufen. das entspricht dann der situation, dass der kindprozess innerhalb des timeouts fertig wurde. wenn sleep zurückkehrt, wird gleich per kill() das kind beendet, SIGCHLD auch in der situation gesandt. du kannst eine globale variable verwenden, die direkt nach dem sleep() aufruf auf 1 gesetzt wird, aber mit 0 initialisiert wurde, um im signal handler zu erkennen, ob das timeout abgelaufen ist (wert ist 1) oder nicht.
diese lösung ist sinnvoll, wenn du den kindprozess nicht selbst schreibst. wenn du das aber tust, könntest du dort alarm() verwenden und SIGALRM im kindprozess abfangen und den kindprozess im signal handler beenden. per exit-status kannst du dann dem elternprozess noch mitteilen, ob das timeout abgelaufen ist oder nicht.
-
so? ne oda?
if (pid == 0) { /* child process */ ... } else { /* parent */ mDidSleepWholeTime = false; pid_t chds = wait(0); sleep(MAX_PROCESS_TIME); mDidSleepWholeTime = true; kill(chds, SIGTERM); exit(0); }
wobei ich dann in der aufrufenden funktion mDidSleepWholeTime checken könnte ...
lg
lordy
-
du hast den signal handler nicht gepostet.
mDidSleepWholeTime wird zu spät auf false gesetzt. es muss schon vor dem fork() (bzw. vor dem installieren des signal handlers für SIGCHLD) auf false gesetzt werden - am besten eben gleich bei der initialisierung. wait() und exit() gehören in den signal handler. noch ein zusatz aber: statt dem exit() machst am besten ein pause(). es könnte nämlich passieren, dass der elternprozess direkt nach dem kill durch ein simples return von der main funktion beendet wird, bevor das os den signal handler aufrufen konnte. pause() wartet auf ein eingehendes signal. sollte das signal vor pause() kommen, wird der handler aber normal aufgerufen und in dem das programm beendet, sodass pause() gar nie aufgerufen wird. es gibt noch die lösung, dass du direkt vor kill() des signal handler wieder entfernst, nach kill() wait() aufrufst und dann exit(). dann brauchst du das pause() nicht.
hmm wenn du eine instant messaging adresse in dein profil schreibst, könnte man dich auch direkt erreich. ist vielleicht einfacher, das realtime zu erklären.
-
ok jetzt schauts so aus ...
if (pid == 0) { /* child process */ /* no errors are reported to the caller from here on */ dup2(outfd[PIPE_WRITE], STDOUT_FILENO); /* redireceting stdout */ dup2(errfd[PIPE_WRITE], STDERR_FILENO); /* redireceting stderr */ close(outfd[PIPE_READ]); /* close reading end */ close(errfd[PIPE_READ]); execl("/bin/sh", "sh", "-c", command, NULL); /* shell call */ LOGGER.log("CProcess::popen2", "execl failed", WARNING); exit(0); /* if excel fails end it */ } else { /* parent */ signal (SIGCHLD, signalHandlerChild); sleep(MAX_PROCESS_TIME); mDidSleepWholeTime = true; kill(0, SIGTERM); } close(outfd[PIPE_WRITE]); /* close writing ends */ close(errfd[PIPE_WRITE]); files[0] = fdopen(outfd[PIPE_READ], "r"); /* return reading end to the caller */ files[1] = fdopen(errfd[PIPE_READ], "r"); if (files[0] == NULL || files[1] == NULL) { LOGGER.log("CProcess::popen2", "fdopen failed", ERROR); } return pid; } void CProcess::signalHandlerChild (int sig) { pid_t statusChild = wait(0); pause(); }
also irgendwo hab ich da noch leichte probleme, zumal er jetzt auch beim binding jammert: signal (SIGCHLD, signalHandlerChild); => error: argument of type ‘void (CProcess::)(int)’ does not match ‘void (*)(int)’, wobei aber signal definitiv als ersten parameter das signal und als zweiten die funktion nimmt?
auf was soll ich den kill jetzt richten? weil pid == 0 wäre ja das child jedoch laut doku heisst pid auf 0 bei kill "sig is sent to every process in the process group of the current process." ...ps
icq nummer hät ich eingetragen ins profil