[pypy-commit] pypy default: hg merge signal-and-thread

arigo noreply at buildbot.pypy.org
Fri Feb 15 19:22:00 CET 2013


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r61281:07f5d48c7467
Date: 2013-02-15 18:21 +0000
http://bitbucket.org/pypy/pypy/changeset/07f5d48c7467/

Log:	hg merge signal-and-thread

	Add "thread.signals_enabled", a context manager. Can be used in a
	non-main thread to enable the processing of signal handlers in that
	other thread.

diff --git a/pypy/interpreter/miscutils.py b/pypy/interpreter/miscutils.py
--- a/pypy/interpreter/miscutils.py
+++ b/pypy/interpreter/miscutils.py
@@ -17,8 +17,14 @@
     def setvalue(self, value):
         self._value = value
 
-    def ismainthread(self):
+    def signals_enabled(self):
         return True
 
+    def enable_signals(self):
+        pass
+
+    def disable_signals(self):
+        pass
+
     def getallvalues(self):
         return {0: self._value}
diff --git a/pypy/module/signal/interp_signal.py b/pypy/module/signal/interp_signal.py
--- a/pypy/module/signal/interp_signal.py
+++ b/pypy/module/signal/interp_signal.py
@@ -61,16 +61,16 @@
         "NOT_RPYTHON"
         AsyncAction.__init__(self, space)
         self.pending_signal = -1
-        self.fire_in_main_thread = False
+        self.fire_in_another_thread = False
         if self.space.config.objspace.usemodules.thread:
             from pypy.module.thread import gil
             gil.after_thread_switch = self._after_thread_switch
 
     @rgc.no_collect
     def _after_thread_switch(self):
-        if self.fire_in_main_thread:
-            if self.space.threadlocals.ismainthread():
-                self.fire_in_main_thread = False
+        if self.fire_in_another_thread:
+            if self.space.threadlocals.signals_enabled():
+                self.fire_in_another_thread = False
                 SignalActionFlag.rearm_ticker()
                 # this occurs when we just switched to the main thread
                 # and there is a signal pending: we force the ticker to
@@ -82,11 +82,7 @@
         n = self.pending_signal
         if n < 0: n = pypysig_poll()
         while n >= 0:
-            if self.space.config.objspace.usemodules.thread:
-                in_main = self.space.threadlocals.ismainthread()
-            else:
-                in_main = True
-            if in_main:
+            if self.space.threadlocals.signals_enabled():
                 # If we are in the main thread, report the signal now,
                 # and poll more
                 self.pending_signal = -1
@@ -97,7 +93,7 @@
                 # Otherwise, arrange for perform() to be called again
                 # after we switch to the main thread.
                 self.pending_signal = n
-                self.fire_in_main_thread = True
+                self.fire_in_another_thread = True
                 break
 
     def set_interrupt(self):
@@ -107,7 +103,6 @@
             # ^^^ may override another signal, but it's just for testing
         else:
             pypysig_pushback(cpy_signal.SIGINT)
-        self.fire_in_main_thread = True
 
 # ____________________________________________________________
 
@@ -204,9 +199,10 @@
     if WIN32 and signum not in signal_values:
         raise OperationError(space.w_ValueError,
                              space.wrap("invalid signal value"))
-    if not space.threadlocals.ismainthread():
+    if not space.threadlocals.signals_enabled():
         raise OperationError(space.w_ValueError,
-                             space.wrap("signal only works in main thread"))
+                             space.wrap("signal only works in main thread "
+                                 "or with thread.enable_signals()"))
     check_signum_in_range(space, signum)
 
     if space.eq_w(w_handler, space.wrap(SIG_DFL)):
@@ -235,10 +231,11 @@
 
     The fd must be non-blocking.
     """
-    if not space.threadlocals.ismainthread():
+    if not space.threadlocals.signals_enabled():
         raise OperationError(
             space.w_ValueError,
-            space.wrap("set_wakeup_fd only works in main thread"))
+            space.wrap("set_wakeup_fd only works in main thread "
+                       "or with thread.enable_signals()"))
     old_fd = pypysig_set_wakeup_fd(fd)
     return space.wrap(intmask(old_fd))
 
diff --git a/pypy/module/thread/__init__.py b/pypy/module/thread/__init__.py
--- a/pypy/module/thread/__init__.py
+++ b/pypy/module/thread/__init__.py
@@ -4,6 +4,7 @@
 
 class Module(MixedModule):
     appleveldefs = {
+        'signals_enabled':        'app_signal.signals_enabled',
     }
 
     interpleveldefs = {
@@ -20,6 +21,8 @@
         'LockType':               'os_lock.Lock',
         '_local':                 'os_local.Local',
         'error':                  'space.fromcache(error.Cache).w_error',
+        '_signals_enter':         'interp_signal.signals_enter',
+        '_signals_exit':          'interp_signal.signals_exit',
     }
 
     def __init__(self, space, *args):
diff --git a/pypy/module/thread/app_signal.py b/pypy/module/thread/app_signal.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/thread/app_signal.py
@@ -0,0 +1,14 @@
+import thread
+
+class SignalsEnabled(object):
+    '''A context manager to use in non-main threads:
+enables receiving signals in a "with" statement.  More precisely, if a
+signal is received by the process, then the signal handler might be
+called either in the main thread (as usual) or within another thread
+that is within a "with signals_enabled:".  This other thread should be
+ready to handle unexpected exceptions that the signal handler might
+raise --- notably KeyboardInterrupt.'''
+    __enter__ = thread._signals_enter
+    __exit__  = thread._signals_exit
+
+signals_enabled = SignalsEnabled()
diff --git a/pypy/module/thread/interp_signal.py b/pypy/module/thread/interp_signal.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/thread/interp_signal.py
@@ -0,0 +1,6 @@
+
+def signals_enter(space):
+    space.threadlocals.enable_signals()
+
+def signals_exit(space, w_ignored1=None, w_ignored2=None, w_ignored3=None):
+    space.threadlocals.disable_signals()
diff --git a/pypy/module/thread/test/test_signal.py b/pypy/module/thread/test/test_signal.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/thread/test/test_signal.py
@@ -0,0 +1,42 @@
+import sys
+
+
+class AppTestMinimal:
+    spaceconfig = dict(usemodules=['thread'])
+
+    def test_signal(self):
+        import thread
+        with thread.signals_enabled:
+            pass
+        # assert did not crash
+
+
+class AppTestThreadSignal:
+    spaceconfig = dict(usemodules=['thread', 'signal'])
+
+    def setup_class(cls):
+        if (not cls.runappdirect or
+                '__pypy__' not in sys.builtin_module_names):
+            import py
+            py.test.skip("this is only a test for -A runs on top of pypy")
+
+    def test_enable_signals(self):
+        import thread, signal, time
+        #
+        interrupted = []
+        lock = thread.allocate_lock()
+        lock.acquire()
+        #
+        def subthread():
+            try:
+                time.sleep(0.25)
+                with thread.signals_enabled:
+                    thread.interrupt_main()
+            except BaseException, e:
+                interrupted.append(e)
+            lock.release()
+        #
+        thread.start_new_thread(subthread, ())
+        lock.acquire()
+        assert len(interrupted) == 1
+        assert 'KeyboardInterrupt' in interrupted[0].__class__.__name__
diff --git a/pypy/module/thread/threadlocals.py b/pypy/module/thread/threadlocals.py
--- a/pypy/module/thread/threadlocals.py
+++ b/pypy/module/thread/threadlocals.py
@@ -9,11 +9,12 @@
 
     def __init__(self):
         self._valuedict = {}   # {thread_ident: ExecutionContext()}
+        self._signalsenabled = {}   # {thread_ident: number-of-times}
         self._cleanup_()
 
     def _cleanup_(self):
         self._valuedict.clear()
-        self._mainthreadident = 0
+        self._signalsenabled.clear()
         self._mostrecentkey = 0        # fast minicaching for the common case
         self._mostrecentvalue = None   # fast minicaching for the common case
 
@@ -33,7 +34,7 @@
         ident = rthread.get_ident()
         if value is not None:
             if len(self._valuedict) == 0:
-                self._mainthreadident = ident
+                self._signalsenabled[ident] = 1    # the main thread is enabled
             self._valuedict[ident] = value
         else:
             try:
@@ -44,8 +45,24 @@
         self._mostrecentkey = ident
         self._mostrecentvalue = value
 
-    def ismainthread(self):
-        return rthread.get_ident() == self._mainthreadident
+    def signals_enabled(self):
+        return rthread.get_ident() in self._signalsenabled
+
+    def enable_signals(self):
+        ident = rthread.get_ident()
+        old = self._signalsenabled.get(ident, 0)
+        self._signalsenabled[ident] = old + 1
+
+    def disable_signals(self):
+        ident = rthread.get_ident()
+        try:
+            new = self._signalsenabled[ident] - 1
+        except KeyError:
+            return
+        if new > 0:
+            self._signalsenabled[ident] = new
+        else:
+            del self._signalsenabled[ident]
 
     def getallvalues(self):
         return self._valuedict
@@ -60,4 +77,5 @@
 
     def reinit_threads(self, space):
         "Called in the child process after a fork()"
-        self._mainthreadident = rthread.get_ident()
+        self._signalsenabled.clear()
+        self.enable_signals()


More information about the pypy-commit mailing list