views:

382

answers:

2

Situation:

I am writing a program in C that maintains a number of threads. Once a thread ends, a new one is created.

Each thread forks - the child runs a process via exec() and the parent waits for it to finish.

In addition, there is a signal handler thread that waits for signals. If SIGINT is detected then it tells the main thread to stop creating threads so eventually all the threads end and the program can exit.

Signals are blocked in all threads except of course the signal handler thread.

Aim:

I want to be able to terminate the program by sending SIGTERM. This would work by stopping the main thread creating new threads and also terminating the running processes created by threads.

Problem:

If signals are blocked in all the threads, how can I send a signal to the running processes to terminate them?

Is there some way to make the spawned processes only receive signals sent from the main program and not signals sent to the main program?

+5  A: 

Create all child processes in a new process group, then send the signal to the group.

Edit:

Here's some minimal code that shows how process can change it's group and control child processes with a signal.

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

pid_t pgid; /* process group to kill */

void terminator( int s ) /* signal handler */
{
    printf( "[%d:%d] received signal %d, exiting\n",
        getpid(), getpgrp(), s );
    exit( 1 );
}

int main() /* program entry */
{
    printf( "[%d:%d] before first fork\n",
        getpid(), getpgrp() );

    switch ( fork()) /* try with and without first fork */
    {
        case -1: err( 1, "fork" );
        case 0: break;
        default: exit( 0 );
    }

    if ( signal( SIGTERM, terminator ) == SIG_ERR )
        err( 1, "signal" );

    if ( setpgrp() < 0 ) err( 1, "setpgrp" );
    if (( pgid = getpgrp()) < 0 ) err( 1, "getpgrp" );

    printf( "[%d:%d -> %d] before second fork\n",
        getpid(), getpgrp(), pgid );

    switch ( fork())
    {
        case -1: err( 1, "fork" );
        case 0: /* child */
        {
            printf( "child [%d:%d]\n", getpid(), getpgrp());
            sleep( 20 );
            break;
        }
        default: /* parent */
        {
            printf( "parent [%d:%d]\n", getpid(), getpgrp());
            sleep( 5 );
            killpg( pgid, SIGTERM );
            sleep( 20 );
        }
    }

    printf( "[%d:%d] exiting\n", getpid(), getpgrp());
    exit( 1 );
}

Nikolai N Fetissov
That's exactly what I did but it doesn't seem to work correctly. The group ID I used was the PID of the main thread - is that right?
SlappyTheFish
Assuming **kill()**, are you sending the signal to **negative** of the group id? You can also use **killpg()**.
Nikolai N Fetissov
Yes, the main process calls this (pgrp is global): pgrp = getpid(); setpgid(0,0);then the child process created in each thread: setpgid(0,pgrp);And I try and kill the processes like this: killpg(pgrp, SIGTERM);
SlappyTheFish
Ok, I see that the groupID of the calling process is the same as its PID, which I use for the groupID of the child processes, which is why the signals never arrive. Any suggestions how to resolve this?
SlappyTheFish
Don't call **setpgid(0,pgrp);** in the child - the process group id is already inherited.
Nikolai N Fetissov
I tried that, doesn't work. Any ideas? The code listing is here: http://www.4pmp.com/threadtest.php
SlappyTheFish
In order to create child processes in a new group, would the main process have to fork one child which then creates the threads that each then fork new processes? Otherwise it seems all processes are in the same group.
SlappyTheFish
I think the rule with threads is that only main thread can change the process group. You'd have to check that against pthreads manual. I'm adding some minimal code to the answer.
Nikolai N Fetissov
I eventually called setsid() after each fork() to unattach the process from the main process and unblocked all signals (as signals were blocked by inheritance from the main thread). I also kept track of the child PIDs and called kill() on each. Thanks for all the help!
SlappyTheFish
A: 
  1. Create all child processes in a new process group, then send the signal to the group. (thanks Nokolai)
  2. Keep records on all processes created and use that to signal them when desired.
Darron