Charles-François Natali <neolo...@free.fr> added the comment:

>> I just noticed something "funny": signal_sigwait doesn't release
>> the GIL, which means that it's pretty much useless :-)
>
> sigwait() is not useless: the signal is not necessary send by another thread. 
> The signal can be send by alarm(), from a child process, by an user, by the 
> kernel.
>

If it doesn't release the GIL, it is useless.
The common usage pattern for sigwait() is to create a dedicated thread
for signals management. If this thread calls sigwait without releasing
the GIL, the whole process is suspended, which is definitely not what
you want.

>> Also, test_sigwait doesn't block the signal before calling sigwait:
>> it happens to work because there's only one thread, but it's
>> undefined behaviour.
>
> test_sigwait() test pass on all 3.x buildbots (some don't have sigwait(), the 
> test is skipped). Sometimes, test_sigwait() is executed with 2 threads, the 
> main thread and Tkinter event loop thread, and the signal is not blocked in 
> any thread.
>

It's mere luck:

    def test_sigwait(self):
        old_handler = signal.signal(signal.SIGALRM, self.handler)
        self.addCleanup(signal.signal, signal.SIGALRM, old_handler)

        signal.alarm(1)
        self.assertEqual(signal.sigwait([signal.SIGALRM]), signal.SIGALRM)

Comment out the first two lines that change the signal handler, and
the test will be killed by SIGALRM's default handler ("Alarm clock").
I tested on Linux, and if a the signal isn't blocked before calling sigwait:
- if a custom signal handler is installed, then it is not called
- if the default signal handler is in place, then it's called (and
with SIGALRM the process gets killed)
This is a typical example of what POSIX calls "undefined behavior".
If pthread_sigmask is called before sigwait, then it works as expected.

If we really wanted to test this properly, the way to go would be to
fork() a child process (that way we're sure there's only one thread),
have it block and sigwait() SIGUSR1 without touching the handler, send
it SIGUSR1, and check its return code.
Patch attached.

----------
Added file: http://bugs.python.org/file22312/test_sigwait.diff

_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue8407>
_______________________________________
--- cpython-302424b84b07/Lib/test/test_signal.py        2011-06-01 
04:39:38.000000000 +0200
+++ cpython/Lib/test/test_signal.py     2011-06-10 10:47:33.521906867 +0200
@@ -601,11 +601,24 @@
     @unittest.skipUnless(hasattr(signal, 'sigwait'),
                          'need signal.sigwait()')
     def test_sigwait(self):
-        old_handler = signal.signal(signal.SIGALRM, self.handler)
-        self.addCleanup(signal.signal, signal.SIGALRM, old_handler)
-
-        signal.alarm(1)
-        self.assertEqual(signal.sigwait([signal.SIGALRM]), signal.SIGALRM)
+        # sigwait must be called with the signal blocked: since the current
+        # process might have several threads running, we fork() a child process
+        # to have a single thread.
+        signum = signal.SIGUSR1
+        pid = os.fork()
+        if pid == 0:
+            # child: block and wait the signal
+            signal.pthread_sigmask(signal.SIG_BLOCK, [signum])
+            res = signal.sigwait([signum])
+            if res == signum:
+                os._exit(0)
+            os._exit(1)
+        else:
+            # parent: let the child some time to wait, send him the signal, and
+            # check it correcty received it
+            time.sleep(0.5)
+            os.kill(pid, signum)
+            self.assertEqual(os.waitpid(pid, 0), (pid, 0))
 
     @unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
                          'need signal.pthread_sigmask()')
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to