Synchronous signals vs. robust code

Peter Milliken peter.milliken at gtech.com
Sun Feb 17 23:37:00 EST 2002


Why not use a language that provides your requirements of signal safety etc
and do a Python API for it? (assuming one doesn't exist) i.e. I find it
somewhat difficult to reconcile your statement about responding to time
critical signals when you propose use of an interpretive language..... :-)
Sounds like you would ideally like real-time language features in a language
that isn't designed for it (well, I'm not sure Guido had visions of Python
being used in real-time applications :-)).

Peter

"Mark Mitchell" <mark at codesourcery.com> wrote in message
news:mailman.1013998768.20770.python-list at python.org...
> Python provides synchronous signals, even though signals are
> asynchronous events at the operating system level.  In many ways,
> this is a Good Thing.  For example, in C you can't do much of anything
> from within a signal handler since you don't know much about the
> current state of the application, but in Python you can do just
> about anything.
>
> In particular, you can throw an exception from a Python signal handler,
> which makes handling signals simple.  In C, you generally have to
> set a flag from within the signal handler, and then poll the flag
> elsewhere; in Python you simply have the signal handler thrown an
> exception and then provide an appropriate exception handler.
>
> But, I'm having a hard time figuring out how to write signal-safe
> code in the presence of synchronous signals.  Consider, for example,
> the following:
>
>   pipe = os.pipe()
>
>   try:
>     # Do stuff with the pipe.
>   finally:
>     # Close the ends of the pipe.
>     os.close(pipe[0])
>     os.close(pipe[1])
>
> This code is supposed to make sure that the file descriptors are
> not leaked; when we exit this scope, the descriptors will be closed,
> even if an exception occurs.  [Note that I am assuming that os.close
> will never thrown an exception when given a valid file descriptor.
> That's not strictly speaking true on some operating systems, but
> let's pretend that it is true.  For the purposes of this discussion,
> the particular functions involved don't matter.]
>
> This code, however, is not signal-safe.  For example, if a signal
> occurs between the two calls to os.close, we will end up not closing
> the second descriptor.  The equivalent C++ code *would* be signal safe;
> an exception cannot be thrown from the signal-handler, so if the program
> is still executing we can be sure that both calls to os.close will
> occur.
>
> One solution, of course, is to make sure that the signal-handlers do
> not throw exceptions.  But that's not a reasonable solution in library
> code; the library can't go around deciding what signal handlers should
> be doing.
>
> Another solution is to install an alternate signal handler around the
> entire try-block.  This alternate signal handler would remember the
> signal; we could then regenerate it at the end of the try-block.  This,
> however, is not very robust; what about multiple signals occurring?  And
> if the signal that occurs is important, we're ignoring it until the end
> of the try-block, which isn't polite.
>
> Another solution would be to provide a module that provides access to
> sigprocmask.  Then, one could explicitly block signals during parts
> of the code where these problems could occur.  For example:
>
>   pipe = os.pipe()
>
>   try:
>     # Do stuff with the pipe.
>   finally:
>     # Block signals.
>     sigprocmask(...)
>     # Close the ends of the pipe.
>     os.close(pipe[0])
>     os.close(pipe[1])
>     # Unblock signals.
>     sigprocmask(...)
>
> This does not work either; there are still at least two race conditions.
> A signal that arrives after we have entered the finally clause, but
> before we have called sigprocmask, would still result in a failure to
> close the descriptors.  Similarly, a signal that occurs after the call
> to os.pipe, but before the try-block is entered would result in us
> skipping the finally clause.  (And moving the try earlier is not valid;
> we can't clean up the descriptors unless we know that the pipe has been
> created.)
>
> Here is another try:
>
>   # Block signals.
>   sigprocmask(...)
>
>   try:
>     pipe = os.pipe()
>
>     try:
>       # Do stuff with the pipe.
>     finally:
>       # Close the ends of the pipe.
>       os.close(pipe[0])
>       os.close(pipe[1])
>   finally:
>     # Unblock signals.
>     sigprocmask(...)
>
> This variant is robust; we are now guaranteed that if the pipe is
> created, the descriptors will be closed.
>
> However, it is still unsatisfactory in that it results in the signal
> being delayed for an arbitrary amount of time; if a time-critical
> signal happens during the try-block, we will not process it until
> much later.
>
> Another solution is to require that the code be run in a thread other
> than the main thread; since only the main thread receives signals,
> the signal-safety problem does not occur.  However, it seems excessive
> to require threads simply to write signal-safe code!  And if you have
> code that depends on, say, getting SIGPIPE, then you have to have some
> way of communicating the signal from the main thread to the thread
> running the code above.
>
> What do Python programmers that need to write truly robust code do
> about this problem?  Is there a solution that I have missed?
>
> If not, would the Powers That Be be amenable to making changes to the
> language to support signal-safety?  One possible fix would be
> except/finally clauses that blocks signals on entry.  For example,
> if there were a finally_block_signals clause, you could do:
>
>   # Block signals.
>   sigprocmask(...)
>
>   try:
>     pipe = os.pipe()
>   except:
>     # Unblock signals.
>     sigprocmask(...)
>     # Reraise the exception.
>     raise
>
>   try:
>     # Unblock signals.
>     sigprocmask(...)
>     # Do stuff with the pipe.
>   finally_block_signals:
>     # Close the pipe ends.
>     os.close(pipe[0])
>     os.close(pipe[1])
>     # Unblock signals.
>     sigprocmask(...) # There needs to be a way of getting the signal
>                      # mask on entry to the finally clause.
>
> This is not particularly pretty, but it would work.
>
> Thoughts?
>
> --
> Mark Mitchell                mark at codesourcery.com
> CodeSourcery, LLC            http://www.codesourcery.com
>





More information about the Python-list mailing list