Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Python > Re: Avoid race condition with Popen.send_signal

Reply
Thread Tools

Re: Avoid race condition with Popen.send_signal

 
 
Cameron Simpson
Guest
Posts: n/a
 
      01-03-2012
On 02Jan2012 20:31, Devin Jeanpierre <> wrote:
| > I think that catching the exception is probably the most Pythonic way.
|
| It's the only correct way.

Indeed, but be precise - chek that it _is_ error 3, or more portably,
errno.ESRCH. POSIX probably mandates that that is a 3, but the symbol
should track the local system if it differs. Example:

import errno
...
try:
...signal...
except OSError, e:
if e.errno == errno.ESRCH:
pass # or maybe an info log message
else:
raise # something else wrong - raise exception anyway

Cheers,
--
Cameron Simpson <> DoD#743
http://www.cskk.ezoshosting.com/cs/

WHAT"S A ""K3WL D00D"" AND WH3R3 CAN 1 G3T S0M3!!!!!!!!!!!????????
- Darren Embry
 
Reply With Quote
 
 
 
 
Adam Skutt
Guest
Posts: n/a
 
      01-03-2012
On Jan 2, 8:44*pm, Cameron Simpson <c...@zip.com.au> wrote:
> On 02Jan2012 20:31, Devin Jeanpierre <jeanpierr...@gmail.com> wrote:
> | > I think that catching the exception is probably the most Pythonic way..
> |
> | It's the only correct way.
>
> Indeed, but be precise - chek that it _is_ error 3, or more portably,
> errno.ESRCH. POSIX probably mandates that that is a 3, but the symbol
> should track the local system if it differs. Example:
>


No. It is possible (however unlikely) for EPERM to be legitimately
returned in this case. Anything other than EINVAL should be
interpreted as "The child process is dead". Hence why you should
avoid sending the signal in the first place: the situations where you
don't run the risk of possibly killing an innocent bystander are
pretty narrow. While unlikely on modern UNiX and Linux, IMO it's best
to avoid the issue altogether whenever possible.

Adam
 
Reply With Quote
 
 
 
 
Cameron Simpson
Guest
Posts: n/a
 
      01-03-2012
On 02Jan2012 19:16, Adam Skutt <> wrote:
| On Jan 2, 8:44Â*pm, Cameron Simpson <c...@zip.com.au> wrote:
| > On 02Jan2012 20:31, Devin Jeanpierre <jeanpierr...@gmail.com> wrote:
| > | > I think that catching the exception is probably the most Pythonic way.
| > |
| > | It's the only correct way.
| >
| > Indeed, but be precise - chek that it _is_ error 3, or more portably,
| > errno.ESRCH. POSIX probably mandates that that is a 3, but the symbol
| > should track the local system if it differs. Example:
|
| No. It is possible (however unlikely) for EPERM to be legitimately
| returned in this case. Anything other than EINVAL should be
| interpreted as "The child process is dead".

Sure. I was more taking the line: catch and accept only the specific
errors you understand. Of course he can catch EPERM also. But any other
variant should at the least generate a warning to stderr or a log -
it is _unexpected_.

I take your point that reraising the exception may be overkill for
failed signal delivery (if that was your point). But I am arguing for
being very careful about what you silently pass as an ok thing.

| Hence why you should
| avoid sending the signal in the first place: the situations where you
| don't run the risk of possibly killing an innocent bystander are
| pretty narrow. While unlikely on modern UNiX and Linux, IMO it's best
| to avoid the issue altogether whenever possible.

Fair enough too. But sometimes you need to nail a rogue child.

Cheers,
--
Cameron Simpson <> DoD#743
http://www.cskk.ezoshosting.com/cs/

Death is life's way of telling you you've been fired. - R. Geis
 
Reply With Quote
 
Jérôme
Guest
Posts: n/a
 
      01-03-2012
Mon, 2 Jan 2012 19:16:50 -0800 (PST)
Adam Skutt a écrit:

> No. It is possible (however unlikely) for EPERM to be legitimately
> returned in this case. Anything other than EINVAL should be
> interpreted as "The child process is dead". Hence why you should
> avoid sending the signal in the first place: the situations where you
> don't run the risk of possibly killing an innocent bystander are
> pretty narrow. While unlikely on modern UNiX and Linux, IMO it's best
> to avoid the issue altogether whenever possible.


Should I understand that Popen.send_signal blindly sends the signal to the
process of PID Popen.pid, and therefore could kill a new process of the same
PID that would have been launched by the same user in another program ?

In other words, if a user launches my python program, which in its turn
launches a subprocess of PID N, then the subprocess dies, then the same user
launches a terminal that gets the same PID (N), what happens if my python
program calls Popen.send_signal(SIGINT) ? Does it kill the terminal ?

If so, I don't see how I can protect myself from that. Checking the process
is alive and then hoping that the time interval for the race condition is so
small that there are few chances for that to happen (because the OS
quarantines PID numbers for a while, for instance) ?

--
Jérôme
 
Reply With Quote
 
Chris Angelico
Guest
Posts: n/a
 
      01-03-2012
On Tue, Jan 3, 2012 at 7:44 PM, Jérôme <> wrote:
> If so, I don't see how I can protect myself from that. Checking the process
> is alive and then hoping that the time interval for the race condition isso
> small that there are few chances for that to happen (because the OS
> quarantines PID numbers for a while, for instance) ?


The probability is extremely small. PIDs are generally allocated
sequentially, and obviously one won't be reallocated until the
previous process has terminated. You're looking at a narrow window of
opportunity between a check and an action; you don't really need to
worry about PID reuse within that window, unless there's a particular
reason to fear it (eg your process is very low priority, or there's a
lot of "process spinning" happening). Under normal circumstances, you
won't see a new process start up with the same PID for some time.

(I can't make a statement on Python's module, though.)

ChrisA
 
Reply With Quote
 
Jérôme
Guest
Posts: n/a
 
      01-03-2012
Tue, 3 Jan 2012 19:58:57 +1100
Chris Angelico a écrit:

> On Tue, Jan 3, 2012 at 7:44 PM, Jérôme <> wrote:
> > If so, I don't see how I can protect myself from that. Checking the
> > process is alive and then hoping that the time interval for the race
> > condition is so small that there are few chances for that to happen
> > (because the OS quarantines PID numbers for a while, for instance) ?

>
> The probability is extremely small. PIDs are generally allocated
> sequentially, and obviously one won't be reallocated until the
> previous process has terminated. You're looking at a narrow window of
> opportunity between a check and an action; you don't really need to
> worry about PID reuse within that window, unless there's a particular
> reason to fear it (eg your process is very low priority, or there's a
> lot of "process spinning" happening). Under normal circumstances, you
> won't see a new process start up with the same PID for some time.
>
> (I can't make a statement on Python's module, though.)


Thanks for clarifying this.

(Out of curiosity, what would be the way to be sure when not in "normal
circumstances" ?)

So I rely on the OS for not allocating a "recently released" PID. However, if
the PID was released long ago, I still need to cover myself up as Popen won't
do it for me.

E.g.:

I have an application that can spawn a subprocess to play a beep. I want it
to kill the subprocess when exiting.

To do so, my close() method must

a/ Check if any subprocess has actually been launched (I store the Popen in
a variable called _beep_process. If Popen has not been called, the variable
is init to 0 and the call to send_signal will fail.)

b/ Check if the process is still alive using Popen.poll() and returncode
(otherwise, I might kill a new process)

c/ Catch the exception in case the process would be dead since the last
check (otherwise, I might get an error from send_signal)


It looks like this :

################################################## ###################
# Close
################################################## ###################
def _close (self, widget):
# If self._beep_process != 0, a subprocess was launched at some point
if (0 != self._beep_process):
print "process launched"
self._beep_process.poll()
# If process still alive
if (None == self._beep_process.returncode):
print "process stil alive"
# Send signal
try:
self._beep_process.send_signal(signal.SIGINT)
except OSError, e:
if e.errno == errno.ESRCH:
print "process just died"
pass # process already dead
else:
raise # something else wrong - raise exception
else:
print "signal sent"
# wait for process to complete
self._beep_process.wait()
else:
print "process already dead"
# Close application
Gtk.main_quit()
################################################## ###################

I would have expected something shorter.

For instance, Popen.send_signal() should not be able to send a signal to a
subprocess that has already returned, should it ? Is there any good reason
for allowing it to do so ? If not, it would spare me check b/ in this example.

--
Jérôme
 
Reply With Quote
 
Adam Skutt
Guest
Posts: n/a
 
      01-03-2012
On Jan 3, 3:44*am, Jérôme <jer...@jolimont.fr> wrote:
> Mon, 2 Jan 2012 19:16:50 -0800 (PST)
> Adam Skutt a écrit:
>
> > No. It is possible (however unlikely) for EPERM to be legitimately
> > returned in this case. *Anything other than EINVAL should be
> > interpreted as "The child process is dead". *Hence why you should
> > avoid sending the signal in the first place: the situations where you
> > don't run the risk of possibly killing an innocent bystander are
> > pretty narrow. *While unlikely on modern UNiX and Linux, IMO it's best
> > to avoid the issue altogether whenever possible.

>
> Should I understand that Popen.send_signal blindly sends the signal to the
> process of PID Popen.pid, and therefore could kill a new process of the same
> PID that would have been launched by the same user in another program ?
>


Or possibly one launched by another user altogether. By /default/,
child processes that terminate become zombies and remain in that state
until their parent reaps them via wait(2) or a related syscall. This
means that until you make such a call, the signal is delivered to the
desired child process. In this case, kill(2) still returns ESRCH
since the child process is a zombie and cannot possibly respond to the
signal.

However, if SIGCHLD has been explicitly ignored or has no SA_NOCLDWAIT
set, child processes are reaped automatically. Likewise, it is
possible to install a signal handler for SIGCHLD that calls wait and
reaps the child processes. Do you know what all of your other Python
modules and extensions do? If so, then you can probably rely on the
default semantics. If not, I'd strongly suggest a more conservative
approach.

Regardless, of whether you can rely on the presence of your zombie
children or not, you should expect the kill(2) call to fail and be
prepared to trap the failure.

Obviously, all of this is rather UNIX / Linux specific.

> If so, I don't see how I can protect myself from that. Checking the process
> is alive and then hoping that the time interval for the race condition isso
> small that there are few chances for that to happen (because the OS
> quarantines PID numbers for a while, for instance) ?


The conservative approach is to use another IPC mechanism to talk to
the process, such as a pipe or a socket. Why are you trying to send
the child process SIGINT in the first place?

Adam
 
Reply With Quote
 
Adam Skutt
Guest
Posts: n/a
 
      01-03-2012
On Jan 3, 3:58*am, Chris Angelico <ros...@gmail.com> wrote:
> On Tue, Jan 3, 2012 at 7:44 PM, Jérôme <jer...@jolimont.fr> wrote:
> > If so, I don't see how I can protect myself from that. Checking the process
> > is alive and then hoping that the time interval for the race condition is so
> > small that there are few chances for that to happen (because the OS
> > quarantines PID numbers for a while, for instance) ?

>
> The probability is extremely small. PIDs are generally allocated
> sequentially, and obviously one won't be reallocated until the
> previous process has terminated. You're looking at a narrow window of
> opportunity between a check and an action; you don't really need to
> worry about PID reuse within that window, unless there's a particular
> reason to fear it (eg your process is very low priority, or there's a
> lot of "process spinning" happening). Under normal circumstances, you
> won't see a new process start up with the same PID for some time.
>


Not all operating systems attempt to generate sequential processes
IDs. The window can be rather large in certain situations, and if you
cannot rely on your child processes becoming zombies and kill being
called only when the child is alive or a zombie, then you should not
be calling kill(2) at all. Killing an unrelated process will be
considered as a bug by users. Hence why I find it easier just to
avoid the problem altogether if I can.

Adam
 
Reply With Quote
 
Adam Skutt
Guest
Posts: n/a
 
      01-03-2012
On Jan 2, 11:53*pm, Cameron Simpson <c...@zip.com.au> wrote:
> On 02Jan2012 19:16, Adam Skutt <ask...@gmail.com> wrote:
> | On Jan 2, 8:44*pm, Cameron Simpson <c...@zip.com.au> wrote:
> | > On 02Jan2012 20:31, Devin Jeanpierre <jeanpierr...@gmail.com> wrote:
> | > | > I think that catching the exception is probably the most Pythonicway.
> | > |
> | > | It's the only correct way.
> | >
> | > Indeed, but be precise - chek that it _is_ error 3, or more portably,
> | > errno.ESRCH. POSIX probably mandates that that is a 3, but the symbol
> | > should track the local system if it differs. Example:
> |
> | No. It is possible (however unlikely) for EPERM to be legitimately
> | returned in this case. *Anything other than EINVAL should be
> | interpreted as "The child process is dead".
>
> Sure. I was more taking the line: catch and accept only the specific
> errors you understand.


That advice really only applies when we have interfaces that don't
fully define their exceptional conditions. That's not the case here.
Though I should correct myself and note that EPERM can only occur if
SIGCHLD is being handled in a non-default fashion. That being said,
I'm not sure any error code should be treated differently from another
here.

> Of course he can catch EPERM also. But any other
> variant should at the least generate a warning to stderr or a log
> it is _unexpected_.


Are they really unexpected though? Even if they are, what is logging
an error going to gain anyone? Even crashing may not be helpful,
especially given that there have been bugs in the past with the return
codes from kill(2). Certainly, I'd be far more concerned about
ensuring my code doesn't accidentally kill the wrong process than
worrying about ensuring it handles the return code from kill(2)
correctly.

> I take your point that reraising the exception may be overkill for
> failed signal delivery (if that was your point). But I am arguing for
> being very careful about what you silently pass as an ok thing.
>


Trapping too few conditions is just as erroneous as trapping too
many. This is especially true when dealing with UNIX system calls.
The right behavior is frequently not as simple as it may appear,
unfortunately.

> | Hence why you should
> | avoid sending the signal in the first place: the situations where you
> | don't run the risk of possibly killing an innocent bystander are
> | pretty narrow. *While unlikely on modern UNiX and Linux, IMO it's best
> | to avoid the issue altogether whenever possible.
>
> Fair enough too. But sometimes you need to nail a rogue child.


Even if I believed that were true, I wouldn't use SIGINT to do it.

Adam
 
Reply With Quote
 
Adam Skutt
Guest
Posts: n/a
 
      01-03-2012
On Jan 3, 4:38*am, Jérôme <jer...@jolimont.fr> wrote:
> I have an application that can spawn a subprocess to play a beep. I want it
> to kill the subprocess when exiting.


Why? You shouldn't need to send a signal to tell the process to
terminate: it should terminate when its stdin is closed or when it's
done playing the beep in the first place. If it doesn't, I would find
a better way to play a beep in the first place. What you're
attempting to do sounds superfluous and unnecessary.

The only thing you ought to need to do is ensure that the child
process is properly reaped if it terminates before your process
terminates.

> To do so, my close() method must
>
> * a/ Check if any subprocess has actually been launched (I store the Popen in
> * a variable called _beep_process. If Popen has not been called, the variable
> * is init to 0 and the call to send_signal will fail.)
>
> * b/ Check if the process is still alive using Popen.poll() and returncode
> * (otherwise, I might kill a new process)
>
> * c/ Catch the exception in case the process would be dead since the last
> * check (otherwise, I might get an error from send_signal)
>


Steps a and c are all that are necessary under standard SIGCHLD
handling. However, all of this should be entirely unnecessary in the
first place. Plus, I have a hard time imagining why something like
'gtk.gdk.beep()' isn't adequate for your needs anyway.

> For instance, Popen.send_signal() should not be able to send a signal to a
> subprocess that has already returned, should it ? Is there any good reason
> for allowing it to do so ? If not, it would spare me check b/ in this example.
>


The problem is being able to distinguish "process is a zombie" from
"process doesn't exist" which both return ESRCH. This is certainly
possible with careful coding but I'm not sure I would bother except in
the simplest programs. Too many libraries do too many questionable
things with signal handlers so I find it much safer and easier just to
avoid the damn things whenever possible.

Adam
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Avoid race condition with Popen.send_signal Jérôme Python 4 01-03-2012 01:56 PM
Example code from SwingWorker documentation could have race condition? lionelv@gmail.com Java 3 02-04-2007 07:27 AM
Can static member cause a race condition? alexvodovoz@gmail.com ASP .Net 1 08-07-2006 12:17 PM
Mega Pixel race is like the Mhz Race Hugo Drax Digital Photography 7 01-12-2004 11:07 AM
race condition question jimjim Java 6 11-02-2003 07:55 PM



Advertisments