[Python-checkins] cpython: faulthandler: one more time, fix usage of locks in the watchdog thread

victor.stinner python-checkins at python.org
Fri Apr 8 12:59:15 CEST 2011


http://hg.python.org/cpython/rev/e409d9beaf6b
changeset:   69206:e409d9beaf6b
user:        Victor Stinner <victor.stinner at haypocalc.com>
date:        Fri Apr 08 12:57:06 2011 +0200
summary:
  faulthandler: one more time, fix usage of locks in the watchdog thread

 * Write a new test to ensure that dump_tracebacks_later() still works if
   it was already called and then cancelled before
 * Don't use a variable to check the status of the thread, only rely on locks
 * The thread only releases cancel_event if it was able to acquire it (if
   the timer was interrupted)
 * The main thread always hold this lock. It is only released when
   faulthandler_thread() is interrupted until this thread exits, or at Python
   exit.

files:
  Lib/test/test_faulthandler.py |  45 ++++++++++-------
  Modules/faulthandler.c        |  57 +++++++++++-----------
  2 files changed, 55 insertions(+), 47 deletions(-)


diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py
--- a/Lib/test/test_faulthandler.py
+++ b/Lib/test/test_faulthandler.py
@@ -352,7 +352,7 @@
         with temporary_filename() as filename:
             self.check_dump_traceback_threads(filename)
 
-    def _check_dump_tracebacks_later(self, repeat, cancel, filename):
+    def _check_dump_tracebacks_later(self, repeat, cancel, filename, loops):
         """
         Check how many times the traceback is written in timeout x 2.5 seconds,
         or timeout x 3.5 seconds if cancel is True: 1, 2 or 3 times depending
@@ -364,42 +364,43 @@
 import faulthandler
 import time
 
-def func(repeat, cancel, timeout):
-    if cancel:
+def func(timeout, repeat, cancel, file, loops):
+    for loop in range(loops):
+        faulthandler.dump_tracebacks_later(timeout, repeat=repeat, file=file)
+        if cancel:
+            faulthandler.cancel_dump_tracebacks_later()
+        time.sleep(timeout * 2.5)
         faulthandler.cancel_dump_tracebacks_later()
-    time.sleep(timeout * 2.5)
-    faulthandler.cancel_dump_tracebacks_later()
 
 timeout = {timeout}
 repeat = {repeat}
 cancel = {cancel}
+loops = {loops}
 if {has_filename}:
     file = open({filename}, "wb")
 else:
     file = None
-faulthandler.dump_tracebacks_later(timeout,
-    repeat=repeat, file=file)
-func(repeat, cancel, timeout)
+func(timeout, repeat, cancel, file, loops)
 if file is not None:
     file.close()
 """.strip()
         code = code.format(
-            filename=repr(filename),
-            has_filename=bool(filename),
+            timeout=TIMEOUT,
             repeat=repeat,
             cancel=cancel,
-            timeout=TIMEOUT,
+            loops=loops,
+            has_filename=bool(filename),
+            filename=repr(filename),
         )
         trace, exitcode = self.get_output(code, filename)
         trace = '\n'.join(trace)
 
         if not cancel:
+            count = loops
             if repeat:
-                count = 2
-            else:
-                count = 1
+                count *= 2
             header = 'Thread 0x[0-9a-f]+:\n'
-            regex = expected_traceback(7, 19, header, count=count)
+            regex = expected_traceback(9, 20, header, count=count)
             self.assertRegex(trace, regex)
         else:
             self.assertEqual(trace, '')
@@ -408,12 +409,17 @@
     @unittest.skipIf(not hasattr(faulthandler, 'dump_tracebacks_later'),
                      'need faulthandler.dump_tracebacks_later()')
     def check_dump_tracebacks_later(self, repeat=False, cancel=False,
-                                  file=False):
+                                    file=False, twice=False):
+        if twice:
+            loops = 2
+        else:
+            loops = 1
         if file:
             with temporary_filename() as filename:
-                self._check_dump_tracebacks_later(repeat, cancel, filename)
+                self._check_dump_tracebacks_later(repeat, cancel,
+                                                  filename, loops)
         else:
-            self._check_dump_tracebacks_later(repeat, cancel, None)
+            self._check_dump_tracebacks_later(repeat, cancel, None, loops)
 
     def test_dump_tracebacks_later(self):
         self.check_dump_tracebacks_later()
@@ -427,6 +433,9 @@
     def test_dump_tracebacks_later_file(self):
         self.check_dump_tracebacks_later(file=True)
 
+    def test_dump_tracebacks_later_twice(self):
+        self.check_dump_tracebacks_later(twice=True)
+
     @unittest.skipIf(not hasattr(faulthandler, "register"),
                      "need faulthandler.register")
     def check_register(self, filename=False, all_threads=False,
diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c
--- a/Modules/faulthandler.c
+++ b/Modules/faulthandler.c
@@ -48,13 +48,14 @@
     int fd;
     PY_TIMEOUT_T timeout_ms;   /* timeout in microseconds */
     int repeat;
-    int running;
     PyInterpreterState *interp;
     int exit;
-    /* released by parent thread when cancel request */
+    /* The main thread always hold this lock. It is only released when
+       faulthandler_thread() is interrupted until this thread exits, or at
+       Python exit. */
     PyThread_type_lock cancel_event;
     /* released by child thread when joined */
-    PyThread_type_lock join_event;
+    PyThread_type_lock running;
 } thread;
 #endif
 
@@ -414,7 +415,7 @@
         st = PyThread_acquire_lock_timed(thread.cancel_event,
                                          thread.timeout_ms, 0);
         if (st == PY_LOCK_ACQUIRED) {
-            /* Cancelled by user */
+            PyThread_release_lock(thread.cancel_event);
             break;
         }
         /* Timeout => dump traceback */
@@ -431,21 +432,22 @@
     } while (ok && thread.repeat);
 
     /* The only way out */
-    PyThread_release_lock(thread.cancel_event);
-    PyThread_release_lock(thread.join_event);
+    PyThread_release_lock(thread.running);
 }
 
 static void
-faulthandler_cancel_dump_tracebacks_later(void)
+cancel_dump_tracebacks_later(void)
 {
-    if (thread.running) {
-        /* Notify cancellation */
-        PyThread_release_lock(thread.cancel_event);
-    }
+    /* notify cancellation */
+    PyThread_release_lock(thread.cancel_event);
+
     /* Wait for thread to join */
-    PyThread_acquire_lock(thread.join_event, 1);
-    PyThread_release_lock(thread.join_event);
-    thread.running = 0;
+    PyThread_acquire_lock(thread.running, 1);
+    PyThread_release_lock(thread.running);
+
+    /* The main thread should always hold the cancel_event lock */
+    PyThread_acquire_lock(thread.cancel_event, 1);
+
     Py_CLEAR(thread.file);
 }
 
@@ -489,7 +491,7 @@
         return NULL;
 
     /* Cancel previous thread, if running */
-    faulthandler_cancel_dump_tracebacks_later();
+    cancel_dump_tracebacks_later();
 
     Py_XDECREF(thread.file);
     Py_INCREF(file);
@@ -501,14 +503,10 @@
     thread.exit = exit;
 
     /* Arm these locks to serve as events when released */
-    PyThread_acquire_lock(thread.join_event, 1);
-    PyThread_acquire_lock(thread.cancel_event, 1);
+    PyThread_acquire_lock(thread.running, 1);
 
-    thread.running = 1;
     if (PyThread_start_new_thread(faulthandler_thread, NULL) == -1) {
-        thread.running = 0;
-        PyThread_release_lock(thread.join_event);
-        PyThread_release_lock(thread.cancel_event);
+        PyThread_release_lock(thread.running);
         Py_CLEAR(thread.file);
         PyErr_SetString(PyExc_RuntimeError,
                         "unable to start watchdog thread");
@@ -521,7 +519,7 @@
 static PyObject*
 faulthandler_cancel_dump_tracebacks_later_py(PyObject *self)
 {
-    faulthandler_cancel_dump_tracebacks_later();
+    cancel_dump_tracebacks_later();
     Py_RETURN_NONE;
 }
 #endif /* FAULTHANDLER_LATER */
@@ -1001,15 +999,15 @@
     }
 #endif
 #ifdef FAULTHANDLER_LATER
-    thread.running = 0;
     thread.file = NULL;
     thread.cancel_event = PyThread_allocate_lock();
-    thread.join_event = PyThread_allocate_lock();
-    if (!thread.cancel_event || !thread.join_event) {
+    thread.running = PyThread_allocate_lock();
+    if (!thread.cancel_event || !thread.running) {
         PyErr_SetString(PyExc_RuntimeError,
                         "could not allocate locks for faulthandler");
         return -1;
     }
+    PyThread_acquire_lock(thread.cancel_event, 1);
 #endif
 
     return faulthandler_env_options();
@@ -1023,14 +1021,15 @@
 
 #ifdef FAULTHANDLER_LATER
     /* later */
-    faulthandler_cancel_dump_tracebacks_later();
+    cancel_dump_tracebacks_later();
     if (thread.cancel_event) {
+        PyThread_release_lock(thread.cancel_event);
         PyThread_free_lock(thread.cancel_event);
         thread.cancel_event = NULL;
     }
-    if (thread.join_event) {
-        PyThread_free_lock(thread.join_event);
-        thread.join_event = NULL;
+    if (thread.running) {
+        PyThread_free_lock(thread.running);
+        thread.running = NULL;
     }
 #endif
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list