fork/exec race condition with signals
Web Server forum
Back To The Forum Home!Search!Private Messaging System

Web Server Talk Web Server Talk > Unix and Linux reviews > Free Unix support > Unix Programming > fork/exec race condition with signals




  Last Thread   Next Thread Next
  Show Printable Version Email this Page Subscribe to this Thread      Post New Thread    Post A Reply      

    fork/exec race condition with signals  
skillzero@gmail.com


View Ip Address Report This Message To A Moderator Edit/Delete Message


 
07-19-06 01:04 PM

I'm seeing a race condition when fork/exec'ing a process. My process
catches SIGTERM to do some cleanup, but also sends SIGTERM to processes
that it fork/exec's (to quit them as needed). The problem is that my
process (the parent) calls fork and then continues its normal
processing. The child of the fork does an exec, but if its parent (my
process) sends that child a SIGTERM in the window between fork and
exec, the child is still running the code of my process so it ends up
running the SIGTERM handler of the child instance of my process. My
SIGTERM handler writes to an internal IPC pipe to tell a thread to
quit. Because file descriptors are inherited, this ends up causing the
thread in the parent process to think it needs to quit when the child
get a SIGTERM in this window (some signal handlers are in libraries so
I can't just check the pid).

I thought I could just block all signals before fork and restore them
in the parent after fork, but it seems sigprocmask is preserved even
across exec so any signals ignored in the parent would end up also
being ignored in the newly exec'd process, which I don't want because
that process may want the default behavior for those signals.

The best I could come up with is to block all signals before fork then
from the child, reset signals I know the parent has ignored and unblock
all signals in the child before calling exec, like this:

sigfillset( &newSet );
sigprocmask( SIG_BLOCK, &newSet, &oldSet );
pid = fork();
if( pid == 0 ) // Child
{
signal( SIGPIPE, SIG_DFL ); // SIGPIPE was ignored in parent

sigfillset( &newSet );
sigprocmask( SIG_UNBLOCK, &newSet, NULL );
exec...
}
else
{
sigprocmask( SIG_SETMASK, &oldSet, NULL );
}

Is this good enough? Is there an easier way? I don't like that I have
to just "know" which signals I've ignored and reset them explicitly
because my process uses lots of libraries that may ignore signals
behind my back. I didn't see any sigset iteration functions. I was
hoping for some way to say "exec this process, but restore all signals
to their defaults".






[ Post a follow-up to this message ]



    Re: fork/exec race condition with signals  
William Ahern


View Ip Address Report This Message To A Moderator Edit/Delete Message


 
07-20-06 12:25 AM

On Wed, 19 Jul 2006 03:27:56 -0700, skillzero@gmail.com wrote:

> I'm seeing a race condition when fork/exec'ing a process. My process
> catches SIGTERM to do some cleanup, but also sends SIGTERM to processes
> that it fork/exec's (to quit them as needed). The problem is that my
> process (the parent) calls fork and then continues its normal processing.
> The child of the fork does an exec, but if its parent (my process) sends
> that child a SIGTERM in the window between fork and exec, the child is
> still running the code of my process so it ends up running the SIGTERM
> handler of the child instance of my process. My SIGTERM handler writes to
> an internal IPC pipe to tell a thread to quit. Because file descriptors
> are inherited, this ends up causing the thread in the parent process to
> think it needs to quit when the child get a SIGTERM in this window (some
> signal handlers are in libraries so I can't just check the pid).
>
> I thought I could just block all signals before fork and restore them in
> the parent after fork, but it seems sigprocmask is preserved even across
> exec so any signals ignored in the parent would end up also being ignored
> in the newly exec'd process, which I don't want because that process may
> want the default behavior for those signals.
>
> The best I could come up with is to block all signals before fork then
> from the child, reset signals I know the parent has ignored and unblock
> all signals in the child before calling exec, like this:
>

That's fine if you're okay missing any SIGTERMs in the interim.

Another solution is to do have the parent pause until the child notifies
that it's in a stable state:

int fd[2]
int n;
char c;

if (0 != pipe(fd]))
/* HANDLE ERROR */;

switch (fork()) {
case -1:
/* HANDLE ERROR */;
case 0: /* CHILD */
while (0 != close(fd[0]) && errno == EINTR)
;;

/*
* Install default signal handlers and masks
*/

/*
* Notify parent. Technically we could skip
* this and simply rely on EOF when we do the
* close.
*/
do {
n = write(fd[1], 'a', 1)
} while (n == -1 && errno == EINTR);

while (0 != close(fd[1] && errno == EINTR)
;;

exec(...);

/* NOT REACHED */
default: /* PARENT */
while (0 != close(fd[1]) && errno == EINTR)
;;

do {
n = read(fd[0], &c, 1);
} while (n == -1 && errno == EINTR);

break;
}







[ Post a follow-up to this message ]



    Re: fork/exec race condition with signals  
skillzero@gmail.com


View Ip Address Report This Message To A Moderator Edit/Delete Message


 
07-20-06 12:25 AM

> That's fine if you're okay missing any SIGTERMs in the interim.

I'm not sure I see how SIGTERMs would be missed? If I block signals
then unblock them, any that occur for the parent during that window
should be pending and delivered when the unblock occurs, right?

>         while (0 != close(fd[1] && errno == EINTR)
>             ;;
>
>         exec(...);

It looks like this would have the same race condition since the child
could still be sent a signal after fork and before exec. Or maybe I'm
misunderstanding something?

After doing some more reading and experimenting, I don't think I can
solve this with sigprocmask because my app is multi-threaded.
sigprocmask doesn't seem to block the signals except maybe in the
current current thread (POSIX says its undefined behavior and my tests
show that I still get the signal even when blocked with
sigprocmask...only from a different thread).

I hope I'm just misunderstanding something, but it appears that
blocking signals is basically broken for multi-threaded apps because
even pthread_sigmask only blocks the calling thread and there's no way
I know of to get all the threads to block signals in all of them.

So what I came up with is to essentially do this:

oldSIGTERM = signal( SIGTERM, SIG_DFL );
pid = fork();
if( pid == 0 ) // Child
{
signal( SIGPIPE, SIG_DFL ); // Parent was ignoring SIGPIPE
exec...
}
else
{
signal( SIGTERM, oldSIGTERM );
}

This has a race condition such that if I receive a SIGTERM in the
parent after setting it to SIG_DFL, it's going to terminate the process
without calling my signal handler, but I couldn't figure out any other
way to temporarily block signals for the parent process. Luckily in my
case, it's a closed system and SIGTERM is only sent manually or when
something serious is wrong such that graceful cleanup isn't critical.






[ Post a follow-up to this message ]



    Sponsored Links  




 





   All times are GMT. The time now is 05:57 AM.      Post New Thread    Post A Reply      
  Last Thread   Next Thread Next


Most Popular forums 

Forum Jump:
Rate This Thread:

Forum Rules:
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is OFF
vB code is ON
Smilies are ON
[IMG] code is OFF
 
Medical and Health forum | Computer Games Reviews | Graphics design forum

Back To The Top
Home | Usercp | Faq | Register