How do I kill linux spawnProcess when the main process suddenly dies? - linux

I have come across a problem with my application and the spawnProcess.
If the main application for some reason dies/is killed then the spawned processes seem to live on and I can't reach them unless I use terminal to kill them via their PIDs. My goal is if the main application dies then the spawned processes should be killed also, somehow.
My code is like this
auto appPid = spawnProcess("path/to/process");
scope(exit){ auto exitcode = wait(appPid);
stderr.writeln(...);}
And if I use the same approach when the main process dies, using wait(thisProcessID) I get an error. "No overload matches". Any ideas how to solve this problem?

Here's some code that will do it on Linux. It doesn't have all the features of the stdlib's spawnProcess, it just shows the bare basics, but expanding it from here isn't hard if you need more.
import core.sys.posix.unistd;
version(linux) {
// this function is Linux-specific
import core.stdc.config;
import core.sys.posix.signal;
// we can tell the kernel to send our child process a signal
// when the parent dies...
extern(C) int prctl(int, c_ulong, c_ulong, c_ulong, c_ulong);
// the constant I pulled out of the C headers
enum PR_SET_PDEATHSIG = 1;
}
pid_t mySpawnProcess(string process) {
if(auto pid = fork()) {
// this branch is the parent, it can return the child pid
// you can:
// import core.sys.posix.sys.wait;
// waitpid(this_ret_value, &status, 0);
// if you want the parent to wait for the child to die
return pid;
} else {
// child
// first, tell it to terminate when the parent dies
prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0);
// then, exec our process
char*[2] args;
char[255] buffer;
// gotta copy the string into another buffer
// so we zero terminate it and have a C style char**...
buffer[0 .. process.length] = process[];
buffer[process.length] = 0;
args[0] = buffer.ptr;
// then call exec to run the new program
execve(args[0], args.ptr, null);
assert(0); // never reached
}
}
void main() {
mySpawnProcess("/usr/bin/cat");
// parent process sleeps for one second, then exits
usleep(1_000_000);
}
So the lower level functions need to be used, but Linux does have a function that does what you need.
Of course, since it sends a signal, your child might want to handle that to close more gracefully than the default termination, but try this program and run ps while it sleeps to see cat running, then notice the cat dies when the parent exits.

Related

Return a value from a Signal handler inside a perl daemon

How do I return a value to a process which initiated a daemon from a signal handler inside a daemon?
sub _fork
{
my $pid = fork;
$pid;
}
sub daemonize_monitor_sigio
{
_fork and return;
SIG{IO} = sub{
print "caught sigio";
$ret = {}
...#do some processing
#wants to return $ret here;
}
while(1)
{
;
}
}
daemoniz_monitor_sigio();
The thing about signal handlers, is they're pretty simple. They're basically an interrupt from the kernel, that's passed to the process.
The handler can alter state within the process. But because you've fork()ed beforehand, you have a parent process and a child process - the two don't have any shared state. So 'signaling' from one to the other, is an completely seperate IPC - at the simplest level - you can send another kill signal to the parent process - and get this pid via getpgrp.
For more complicated IPCs though, you're looking at... well, reading the perlipc doc, and figuring out what's most appropriate.

ISSUES with SIGCONT and waitpid() in LINUX

I'll try to keep it simple. I'm currently replicating a shell for LINUX. I use a linked list structure "job_list" to store all background processes. If a background process is terminated, then it is removed from the list. If a background process is suspended, its status within the list is changed from BACKGROUND to STOPPED. If the process is reawakened (through a SIGCONT signal), then the idea is that the process state inside the list should be changed back to BACKGROUND.
My problem is the following: when I send a SIGSTOP signal to a process, section //1 is executed and its change of state is successfully registered in the list. However, when I reawaken that same process using a SIGCONT signal, WIFCONTINUED(status) will return false, but WIFEXITED(status) will always return true. Consequently, section //3 is executed and the process is removed from the list.
What could be wrong? Thanks in advance.
void sigchld_handler (){
block_SIGCHLD();
job *item;
int l_size = list_size(job_list);
int i, new_pid, pid_wait, status, info;
enum status status_res;
for (i = 1; i <= l_size; i++){
item = get_item_bypos(job_list, i);
new_pid = item->pgid;
pid_wait = waitpid(new_pid, &status, WUNTRACED | WNOHANG);
if (WIFSTOPPED(status)){
//1
printf("****SUSPENDED\n");
item->state = STOPPED;
}else if (WIFCONTINUED(status)){
//2
printf("****CONTINUED\n");
item->state = BACKGROUND;
}else if (WIFEXITED(status)){
//3
printf("****EXITED\n");
l_size--;
i--;
delete_job(job_list, item);
}
}
print_job_list(job_list);
unblock_SIGCHLD();
}
You appear to be missing the WCONTINUED value in your call to waitpid.
From the waitpid specification:
pid_t waitpid(pid_t pid, int *stat_loc, int options);
The options argument is constructed from the bitwise-inclusive OR of zero or more of the following flags, defined in the header:
WCONTINUED
The waitpid() function shall report the status of any continued child process specified by pid whose status has not been reported since it continued from a job control stop.

How to keep track of child processes

So my program spawns a number of child processes in response to certain events, and I'm doing something ike this to keep track and kill them upon program exit (Perl syntax):
my %children = ();
# this will be called upon exit
sub kill_children {
kill 'INT' => keys %children;
exit;
}
# main code
while(1) {
...
my $child = fork();
if ($child > 0) {
$children{$child} = 1;
} elsif ($child == 0) {
# do child work ...
exit();
} else {
# handle the error
}
}
So the idea is as above. However, there's a blatant race condition there, in that a given child can start and terminate before the father has a chance to run and record its pid in the %children hash. So the father may end up thinking that a given pid belongs to an active child, even if this child has terminated.
Is there a way to do what I'm trying to accomplish in a safe way?
Edit: To better keep track of children, the code can be extended as follows (which however also suffer of the exact same race condition, so that's why I didn't write it fully in the first place):
my %children = ();
sub reap {
my $child;
while (($child = waitpid(-1, WNOHANG)) > 0) {
#print "collecting dead child $child\n";
delete $children{$child};
}
}
$SIG{CHLD} = \&reap;
# this will be called upon exit
sub kill_children {
local $SIG{CHLD} = 'IGNORE';
kill 'INT' => keys %children;
exit;
}
# main code
while(1) {
...
my $child = fork();
if ($child > 0) {
$children{$child} = 1;
} elsif ($child == 0) {
# do child work ...
exit();
} else {
# handle the error
}
}
Even in this case, the contents of %children may not reflect the actual active children.
Edit 2: I found this question, which is exactly about the same problem. I like the solution suggested in there.
On UNIX it's not a race condition. This is the standard way to handle fork(). When the child process exits, its status is changed to "terminated"; it becomes a zombie. It still has an entry in the process table until the parent process calls one of the wait functions. Only after that is the dead process really removed.
Even if the parent sets itself up to ignore SIGCHLD, it still wouldn't qualify as a race condition; the parent would just have a PID that's not valid anymore. In that case, wait() would return ECHILD. But setting SIGCHLD would free up a child's PID, possibly leading to the parent trying to kill a process that is not a child.
On Windows, which doesn't have a fork call, it is emulated by creating a thread in the perl process. See perlfork. I'm not knowlegable enough about Windows to opinionate about if that could cause a race condition, but I suspect not.

SIGINT signal re-install in linux

I am writing a program dealing with Linux signals. To be more specific, I want to re-install signal SIGINT in child process, only to find that it doesn't work.
Here is a simpler version of my code:
void handler(int sig){
//do something
exit(0);
}
void handler2(int sig){
//do something
exit(0);
}
int main(){
signal(SIGINT, handler);
if ((pid = fork()) == 0) {
signal(SIGINT, handler2); // re-install signal SIGINT
// do something that takes some time
printf("In child process:\n");
execve("foo", argv, environ); // foo is a executable in local dir
exit(0);
}else{
int status;
waitpid(pid, &status, 0); // block itself waiting for child procee to exit
}
return 0;
}
When shell is printing "In child process:", I press ctrl+c. I find that function handler is executed without problem, but handler2 is never executed.
Could you help me with this bug in my code?
Update:
I want the child process to receive SIGINT signal during foo running process, is that possible?
It is not a bug - calling execve has replaced the running binary image. The function handler2() (and any other function of your binary) is no longer mapped in the program memory having been replaced by the image of "foo" and therefore all signal settings are replaced to a default.
If you wish the signal handler to be active during "foo" run, you have to:
make sure the handler function is mapped into the memory of foo
a signal handler is registered after "foo" starts.
One way to do this is to create a shared library that contains the signal handler and an init function that is defined as a constructor that registers said signal handler and force it into the "foo" memory by manipulating the environment under which you execve foo (the environ variable) to include
LD_PRELOAD=/path/to/shared_library.so
#gby's anwser has given comprehensive background knowlegde. I am here to give another solution without shared library.
Every time child process stops or terminates, parent process will receive SIGCHLD. You can handler this SIGCHLD signal to know if child process was terminated by SIGINT. In your handler:
pid_t pid = waitpid(pid_t pid,int * status,int options)
You can get status of child process through waitpid function.
if(WIFSIGNALED(status) && (pid == child_pid)){
if(WTERMSIG(status) == SIGINT){
// now you know your foo has received SIGINT.
// do whatever you like.
}
}

Fork and Zombie process

I am trying some simple code on fork. When I give code like this, it works fine. It will print
I am the child
I am the parent
and then waits for 30 seconds. I understand this is due to switching between these two process. First child executes then parent and then child...
#include<stdio.h>
#include<stdlib.h>
main()
{
int pid;
pid=fork();
if(pid==0)
{
printf("\nI am the child\n");
sleep(30);
exit(0);
}
if(pid>0)
{
printf("\nI am the parent\n");
wait();
}
}
But when I gave like (without wait in parent)
#include<stdio.h>
#include<stdlib.h>
main()
{
int pid;
pid=fork();
if(pid==0)
{
printf("\nI am the child\n");
sleep(30);
exit(0);
}
if(pid>0)
{
printf("\nI am the parent\n");
}
}
it just prints
I am the child
I am the parent
and exits ( no waiting for 30 seconds).
So is it because without wait call parent exits and child still executing? But why is it not showing up in terminal (the waiting)?
Whether parent becomes zombie here?
Your observations are correct.
The terminal waits for the original processes (which is the parent) to exit. It doesn't wait for any child processes to exit.
Zombies: A process is a zombie if it has exited but its parent has not called wait() on it.
In your case, the parent does not become a zombie because the terminal is waiting for it.
Yes.
The grandparent never waits on children; children without a parent are reparented to init.
No, it dies.
The shell is waiting for the parent to finish but not the child.
The child becomes a zombie after 30 seconds (that the shell will clean up with the next prompt).
On the second example the child becomes orphan because the parent had returned while the child is alive. Orphan process belong to init and not the shell, that's why it's not showing up. There is no zombies in your two code samples. Zombies are when a parent is alive an child is dead and the parent doesnt call wait the child becomes zombie.

Resources