scarier INTERRUPT-THREAD and TERMINATE-THREAD documentation
authorNikodemus Siivola <nikodemus@random-state.net>
Thu, 17 Nov 2011 15:06:04 +0000 (17:06 +0200)
committerNikodemus Siivola <nikodemus@random-state.net>
Thu, 17 Nov 2011 17:08:58 +0000 (19:08 +0200)
  Try to put the fear of asynch unwinds into the reader.

src/code/target-thread.lisp

index afb63cc..1bff9ad 100644 (file)
@@ -1325,17 +1325,59 @@ change."
 
 (defun interrupt-thread (thread function)
   #!+sb-doc
-  "Interrupt the live THREAD and make it run FUNCTION. A moderate
-degree of care is expected for use of INTERRUPT-THREAD, due to its
-nature: if you interrupt a thread that was holding important locks
-then do something that turns out to need those locks, you probably
-won't like the effect. FUNCTION runs with interrupts disabled, but
-WITH-INTERRUPTS is allowed in it. Keep in mind that many things may
-enable interrupts (GET-MUTEX when contended, for instance) so the
-first thing to do is usually a WITH-INTERRUPTS or a
-WITHOUT-INTERRUPTS. Within a thread interrupts are queued, they are
-run in same the order they were sent."
-  #!+win32
+  "Interrupt THREAD and make it run FUNCTION.
+
+The interrupt is asynchronous, and can occur anywhere with the exception of
+sections protected using SB-SYS:WITHOUT-INTERRUPTS.
+
+FUNCTION is called with interrupts disabled, under
+SB-SYS:ALLOW-WITH-INTERRUPTS. Since functions such as GRAB-MUTEX may try to
+enable interrupts internally, in most cases FUNCTION should either enter
+SB-SYS:WITH-INTERRUPTS to allow nested interrupts, or
+SB-SYS:WITHOUT-INTERRUPTS to prevent them completely.
+
+When a thread receives multiple interrupts, they are executed in the order
+they were sent -- first in, first out.
+
+This means that a great degree of care is required to use INTERRUPT-THREAD
+safely and sanely in a production environment. The general recommendation is
+to limit uses of INTERRUPT-THREAD for interactive debugging, banning it
+entirely from production environments -- it is simply exceedingly hard to use
+correctly.
+
+With those caveats in mind, what you need to know when using it:
+
+ * If calling FUNCTION causes a non-local transfer of control (ie. an
+   unwind), all normal cleanup forms will be executed.
+
+   However, if the interrupt occurs during cleanup forms of an UNWIND-PROTECT,
+   it is just as if that had happened due to a regular GO, THROW, or
+   RETURN-FROM: the interrupted cleanup form and those following it in the
+   same UNWIND-PROTECT do not get executed.
+
+   SBCL tries to keep its own internals asynch-unwind-safe, but this is
+   frankly an unreasonable expectation for third party libraries, especially
+   given that asynch-unwind-safety does not compose: a function calling
+   only asynch-unwind-safe function isn't automatically asynch-unwind-safe.
+
+   This means that in order for an asych unwind to be safe, the entire
+   callstack at the point of interruption needs to be asynch-unwind-safe.
+
+ * In addition to asynch-unwind-safety you must consider the issue of
+   re-entrancy. INTERRUPT-THREAD can cause function that are never normally
+   called recursively to be re-entered during their dynamic contour,
+   which may cause them to misbehave. (Consider binding of special variables,
+   values of global variables, etc.)
+
+Take togather, these two restrict the \"safe\" things to do using
+INTERRUPT-THREAD to a fairly minimal set. One useful one -- exclusively for
+interactive development use is using it to force entry to debugger to inspect
+the state of a thread:
+
+  (interrupt-thread thread #'break)
+
+Short version: be careful out there."
+ #!+win32
   (declare (ignore thread))
   #!+win32
   (with-interrupt-bindings
@@ -1360,8 +1402,43 @@ run in same the order they were sent."
 
 (defun terminate-thread (thread)
   #!+sb-doc
-  "Terminate the thread identified by THREAD, by causing it to run
-SB-EXT:QUIT - the usual cleanup forms will be evaluated"
+  "Terminate the thread identified by THREAD, by interrupting it and causing
+it to call SB-EXT:QUIT.
+
+The unwind caused by TERMINATE-THREAD is asynchronous, meaning that eg. thread
+executing
+
+  (let (foo)
+     (unwind-protect
+         (progn
+            (setf foo (get-foo))
+            (work-on-foo foo))
+       (when foo
+         ;; An interrupt occurring inside the cleanup clause
+         ;; will cause cleanups from the current UNWIND-PROTECT
+         ;; to be dropped.
+         (release-foo foo))))
+
+might miss calling RELEASE-FOO despite GET-FOO having returned true if the
+interrupt occurs inside the cleanup clause, eg. during execution of
+RELEASE-FOO.
+
+Thus, in order to write an asynch unwind safe UNWIND-PROTECT you need to use
+WITHOUT-INTERRUPTS:
+
+  (let (foo)
+    (sb-sys:without-interrupts
+      (unwind-protect
+          (progn
+            (setf foo (sb-sys:allow-with-interrupts
+                        (get-foo)))
+            (sb-sys:with-local-interrupts
+              (work-on-foo foo)))
+       (when foo
+         (release-foo foo)))))
+
+Since most libraries using UNWIND-PROTECT do not do this, you should never
+assume that unknown code can safely be terminated using TERMINATE-THREAD."
   (interrupt-thread thread 'sb!ext:quit))
 
 (define-alien-routine "thread_yield" int)