0.9.4.1: thread allocation
authorGabor Melis <mega@hotpop.com>
Fri, 26 Aug 2005 19:01:36 +0000 (19:01 +0000)
committerGabor Melis <mega@hotpop.com>
Fri, 26 Aug 2005 19:01:36 +0000 (19:01 +0000)
  * *CURRENT-THREAD* is now properly unbound (don't do bind_variable
    without unbind), the workaround from 0.9.3.75 is removed
  * also *CURRENT-THREAD* is temporarily bound to nil in the parent
    thread to avoid the child inheriting the value from the parent
    that could unnecessarily keep the parent thread object around
    until the child exited
  * free threads' interrupt_data when necessary
  * made all_threads_lock a mutex instead of a spinlock to speed
    start_the_world up
  * minor cleanups

NEWS
src/code/target-thread.lisp
src/runtime/interrupt.c
src/runtime/thread.c
tests/threads.impure.lisp
version.lisp-expr

diff --git a/NEWS b/NEWS
index 162a188..38cd37c 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,9 @@
 ;;;; -*- coding: utf-8; -*-
+changes in sbcl-0.9.5 relative to sbcl-0.9.4:
+  * threads
+    ** bug fix: parent thread now can be gc'ed even with a live
+       child thread
+
 changes in sbcl-0.9.4 relative to sbcl-0.9.3:
   * new port: the Solaris operating system on x86 processors is now
     mostly supported, though some rough edges in the environment
index 639bd2d..ba38649 100644 (file)
@@ -93,7 +93,7 @@ in future versions."
       system-area-pointer
     (lisp-fun-address unsigned-long))
 
-  (define-alien-routine "block_deferrable_signals_and_inhibit_gc"
+  (define-alien-routine "block_blockable_signals"
     void)
 
   (define-alien-routine reap-dead-thread void
@@ -467,43 +467,47 @@ returns the thread exits."
          (setup-p nil)
          (real-function (coerce function 'function))
          (thread-sap
-          (%create-thread
-           (sb!kernel:get-lisp-obj-address
-            (lambda ()
-              ;; FIXME: use semaphores?
-              (loop until setup-p)
-              ;; in time we'll move some of the binding presently done in C
-              ;; here too
-              (let ((*current-thread* thread)
-                    (sb!kernel::*restart-clusters* nil)
-                    (sb!kernel::*handler-clusters* nil)
-                    (sb!kernel::*condition-restarts* nil)
-                    (sb!impl::*descriptor-handlers* nil)) ; serve-event
-                ;; can't use handling-end-of-the-world, because that flushes
-                ;; output streams, and we don't necessarily have any (or we
-                ;; could be sharing them)
-                (unwind-protect
-                     (catch 'sb!impl::toplevel-catcher
-                       (catch 'sb!impl::%end-of-the-world
-                         (with-simple-restart
-                             (terminate-thread
-                              (format nil "~~@<Terminate this thread (~A)~~@:>"
-                                      *current-thread*))
-                           ;; now that most things have a chance to work
-                           ;; properly without messing up other threads, it's
-                           ;; time to enable signals
-                           (sb!unix::reset-signal-mask)
-                           (unwind-protect
-                                (funcall real-function)
-                             ;; we're going down, can't handle
-                             ;; interrupts sanely anymore
-                             (block-deferrable-signals-and-inhibit-gc)))))
-                  ;; and remove what can be the last references to the
-                  ;; thread object
-                  (handle-thread-exit thread)
-                  (setq *current-thread* nil)
-                  0))
-              (values))))))
+          ;; don't let the child inherit *CURRENT-THREAD* because that
+          ;; can prevent gc'ing this thread while the child runs
+          (let ((*current-thread* nil))
+            (%create-thread
+             (sb!kernel:get-lisp-obj-address
+              (lambda ()
+                ;; FIXME: use semaphores?
+                (loop until setup-p)
+                ;; in time we'll move some of the binding presently done in C
+                ;; here too
+                (let ((*current-thread* thread)
+                      (sb!kernel::*restart-clusters* nil)
+                      (sb!kernel::*handler-clusters* nil)
+                      (sb!kernel::*condition-restarts* nil)
+                      (sb!impl::*descriptor-handlers* nil)) ; serve-event
+                  ;; can't use handling-end-of-the-world, because that flushes
+                  ;; output streams, and we don't necessarily have any (or we
+                  ;; could be sharing them)
+                  (unwind-protect
+                       (catch 'sb!impl::toplevel-catcher
+                         (catch 'sb!impl::%end-of-the-world
+                           (with-simple-restart
+                               (terminate-thread
+                                (format nil
+                                        "~~@<Terminate this thread (~A)~~@:>"
+                                        *current-thread*))
+                             ;; now that most things have a chance to
+                             ;; work properly without messing up other
+                             ;; threads, it's time to enable signals
+                             (sb!unix::reset-signal-mask)
+                             (unwind-protect
+                                  (funcall real-function)
+                               ;; we're going down, can't handle
+                               ;; interrupts sanely anymore
+                               (let ((sb!impl::*gc-inhibit* t))
+                                 (block-blockable-signals)
+                                 ;; and remove what can be the last
+                                 ;; reference to this thread
+                                 (handle-thread-exit thread))))))
+                    0))
+                (values)))))))
     (when (sb!sys:sap= thread-sap (sb!sys:int-sap 0))
       (error "Can't create a new thread"))
     (setf (thread-%sap thread) thread-sap)
index 8bc1e8b..ecf0464 100644 (file)
@@ -157,17 +157,7 @@ void reset_signal_mask ()
     thread_sigmask(SIG_SETMASK,&new,0);
 }
 
-void block_deferrable_signals_and_inhibit_gc ()
-{
-    struct thread *thread=arch_os_get_current_thread();
-    sigset_t block;
-    sigemptyset(&block);
-    sigaddset_deferrable(&block);
-    thread_sigmask(SIG_BLOCK, &block, 0);
-    bind_variable(GC_INHIBIT,T,thread);
-}
-
-static void block_blockable_signals ()
+void block_blockable_signals ()
 {
     sigset_t block;
     sigemptyset(&block);
index 9e1b7a6..2324ca7 100644 (file)
 
 int dynamic_values_bytes=4096*sizeof(lispobj);  /* same for all threads */
 struct thread * volatile all_threads;
-volatile lispobj all_threads_lock;
 extern struct interrupt_data * global_interrupt_data;
 extern int linux_no_threads_p;
 
 #ifdef LISP_FEATURE_SB_THREAD
+
+pthread_mutex_t all_threads_lock = PTHREAD_MUTEX_INITIALIZER;
+
 /* When trying to get all_threads_lock one should make sure that
  * sig_stop_for_gc is not blocked. Else there would be a possible
  * deadlock: gc locks it, other thread blocks signals, gc sends stop
@@ -59,13 +61,13 @@ void check_sig_stop_for_gc_can_arrive_or_lose()
         check_sig_stop_for_gc_can_arrive_or_lose(); \
         FSHOW_SIGNAL((stderr,"/%s:waiting on lock=%ld, thread=%lu\n",name, \
                all_threads_lock,arch_os_get_current_thread()->os_thread)); \
-        get_spinlock(&all_threads_lock,(long)arch_os_get_current_thread()); \
+        pthread_mutex_lock(&all_threads_lock); \
         FSHOW_SIGNAL((stderr,"/%s:got lock, thread=%lu\n", \
                name,arch_os_get_current_thread()->os_thread));
 
 #define RELEASE_ALL_THREADS_LOCK(name) \
         FSHOW_SIGNAL((stderr,"/%s:released lock\n",name)); \
-        release_spinlock(&all_threads_lock); \
+        pthread_mutex_unlock(&all_threads_lock); \
         thread_sigmask(SIG_SETMASK,&_oldset,0); \
     }
 #endif
@@ -127,12 +129,27 @@ new_thread_trampoline(struct thread *th)
 }
 #endif /* LISP_FEATURE_SB_THREAD */
 
+#define THREAD_STRUCT_SIZE (THREAD_CONTROL_STACK_SIZE + BINDING_STACK_SIZE + \
+                            ALIEN_STACK_SIZE + dynamic_values_bytes + \
+                            32 * SIGSTKSZ)
+
+static void
+free_thread_struct(struct thread *th)
+{
+    if (th->interrupt_data)
+        os_invalidate((os_vm_address_t) th->interrupt_data,
+                      (sizeof (struct interrupt_data)));
+    os_invalidate((os_vm_address_t) th->control_stack_start,
+                  THREAD_STRUCT_SIZE);
+}
+
 /* this is called from any other thread to create the new one, and
  * initialize all parts of it that can be initialized from another
  * thread
  */
 
-struct thread * create_thread_struct(lispobj initial_function) {
+static struct thread *
+create_thread_struct(lispobj initial_function) {
     union per_thread_data *per_thread;
     struct thread *th=0;        /*  subdue gcc */
     void *spaces=0;
@@ -140,12 +157,7 @@ struct thread * create_thread_struct(lispobj initial_function) {
     /* may as well allocate all the spaces at once: it saves us from
      * having to decide what to do if only some of the allocations
      * succeed */
-    spaces=os_validate(0,
-                       THREAD_CONTROL_STACK_SIZE+
-                       BINDING_STACK_SIZE+
-                       ALIEN_STACK_SIZE+
-                       dynamic_values_bytes+
-                       32*SIGSTKSZ);
+    spaces=os_validate(0, THREAD_STRUCT_SIZE);
     if(!spaces)
          return NULL;
     per_thread=(union per_thread_data *)
@@ -244,6 +256,10 @@ struct thread * create_thread_struct(lispobj initial_function) {
 
     th->interrupt_data = (struct interrupt_data *)
         os_validate(0,(sizeof (struct interrupt_data)));
+    if (!th->interrupt_data) {
+        free_thread_struct(th);
+        return 0;
+    }
     if(all_threads)
         memcpy(th->interrupt_data,
                arch_os_get_current_thread()->interrupt_data,
@@ -332,11 +348,7 @@ struct thread *create_thread(lispobj initial_function) {
     if (success)
         link_thread(th,kid_tid);
     else
-        os_invalidate((os_vm_address_t) th->control_stack_start,
-                      ((sizeof (lispobj))
-                       * (th->control_stack_end-th->control_stack_start)) +
-                      BINDING_STACK_SIZE+ALIEN_STACK_SIZE+dynamic_values_bytes+
-                      32*SIGSTKSZ);
+        free_thread_struct(th);
 
     RELEASE_ALL_THREADS_LOCK("create_thread")
 
@@ -358,7 +370,6 @@ void reap_dead_thread(struct thread *th)
         sigaddset_blockable(&newset);
         thread_sigmask(SIG_BLOCK, &newset, &oldset);
         gc_alloc_update_page_tables(0, &th->alloc_region);
-        release_spinlock(&all_threads_lock);
         thread_sigmask(SIG_SETMASK,&oldset,0);
     }
 #endif
@@ -372,11 +383,7 @@ void reap_dead_thread(struct thread *th)
     RELEASE_ALL_THREADS_LOCK("reap_dead_thread")
     if(th->tls_cookie>=0) arch_os_thread_cleanup(th);
     gc_assert(pthread_join(th->os_thread,NULL)==0);
-    os_invalidate((os_vm_address_t) th->control_stack_start,
-                  ((sizeof (lispobj))
-                   * (th->control_stack_end-th->control_stack_start)) +
-                  BINDING_STACK_SIZE+ALIEN_STACK_SIZE+dynamic_values_bytes+
-                  32*SIGSTKSZ);
+    free_thread_struct(th);
 }
 
 /* Send the signo to os_thread, retry if the rt signal queue is
@@ -469,16 +476,16 @@ void gc_stop_the_world()
     FSHOW_SIGNAL((stderr,"/gc_stop_the_world:waiting on lock, thread=%lu\n",
                   th->os_thread));
     /* keep threads from starting while the world is stopped. */
-    get_spinlock(&all_threads_lock,(long)th);
+    pthread_mutex_lock(&all_threads_lock); \
     FSHOW_SIGNAL((stderr,"/gc_stop_the_world:got lock, thread=%lu\n",
                   th->os_thread));
     /* stop all other threads by sending them SIG_STOP_FOR_GC */
     for(p=all_threads; p; p=p->next) {
         while(p->state==STATE_STARTING) sched_yield();
         if((p!=th) && (p->state==STATE_RUNNING)) {
-            status=kill_thread_safely(p->os_thread,SIG_STOP_FOR_GC);
             FSHOW_SIGNAL((stderr,"/gc_stop_the_world: suspending %lu\n",
                           p->os_thread));
+            status=kill_thread_safely(p->os_thread,SIG_STOP_FOR_GC);
             if (status==ESRCH) {
                 /* This thread has exited. */
                 gc_assert(p->state==STATE_DEAD);
@@ -533,7 +540,7 @@ void gc_start_the_world()
      * SIG_STOP_FOR_GC wouldn't need to be a rt signal. That has some
      * performance implications, but does away with the 'rt signal
      * queue full' problem. */
-    release_spinlock(&all_threads_lock);
+    pthread_mutex_unlock(&all_threads_lock); \
     FSHOW_SIGNAL((stderr,"/gc_start_the_world:end\n"));
 }
 #endif
index 48bf07e..5aa5192 100644 (file)
@@ -13,6 +13,9 @@
 
 (in-package "SB-THREAD") ; this is white-box testing, really
 
+(defun wait-for-threads (threads)
+  (loop while (some #'sb-thread:thread-alive-p threads) do (sleep 0.01)))
+
 (assert (eql 1 (length (list-all-threads))))
 
 (assert (eq *current-thread*
@@ -54,7 +57,8 @@
     ;; Start NTHREADS idle threads.
     (dotimes (i nthreads)
       (sb-thread:make-thread (lambda ()
-                               (sb-thread:condition-wait queue mutex)
+                               (with-mutex (mutex)
+                                 (sb-thread:condition-wait queue mutex))
                                (sb-ext:quit))))
     (let ((start-time (get-internal-run-time)))
       (funcall function)
                (format t "done ~A~%" *current-thread*))))
     (let ((kid1 (make-thread #'run))
           (kid2 (make-thread #'run)))
-      (format t "contention ~A ~A~%" kid1 kid2))))
+      (format t "contention ~A ~A~%" kid1 kid2)
+      (wait-for-threads (list kid1 kid2)))))
 
 (defun test-interrupt (function-to-interrupt &optional quit-p)
   (let ((child  (make-thread function-to-interrupt)))
 (test-interrupt #'loop-forever :quit)
 
 (let ((child (test-interrupt (lambda () (loop (sleep 2000))))))
-  (terminate-thread child))
+  (terminate-thread child)
+  (wait-for-threads (list child)))
 
 (let ((lock (make-mutex :name "loctite"))
       child)
     (sleep 5)
     (interrupt-thread child (lambda () (format t "l ~A~%" (mutex-value lock))))
     (format t "parent releasing lock~%"))
-  (terminate-thread child))
+  (terminate-thread child)
+  (wait-for-threads (list child)))
 
 (format t "~&locking test done~%")
 
                           (sleep (random 0.1d0))
                           (princ ".")
                           (force-output)
-                          (sb-thread:interrupt-thread
-                           thread
-                           (lambda ()))))))))
-      (loop while (some #'thread-alive-p killers) do (sleep 0.1))
-      (sb-thread:terminate-thread thread)))
+                          (sb-thread:interrupt-thread thread (lambda ()))))))))
+      (wait-for-threads killers)
+      (sb-thread:terminate-thread thread)
+      (wait-for-threads (list thread))))
   (sb-ext:gc :full t))
 
 (format t "~&multi interrupt test done~%")
 (let ((c (make-thread (lambda () (loop (alloc-stuff))))))
   ;; NB this only works on x86: other ports don't have a symbol for
   ;; pseudo-atomic atomicity
-  (format t "new thread ~A~%" c)
   (dotimes (i 100)
     (sleep (random 0.1d0))
     (interrupt-thread c
                         (princ ".") (force-output)
                         (assert (eq (thread-state *current-thread*) :running))
                         (assert (zerop SB-KERNEL:*PSEUDO-ATOMIC-ATOMIC*)))))
-  (terminate-thread c))
+  (terminate-thread c)
+  (wait-for-threads (list c)))
 
 (format t "~&interrupt test done~%")
 
     (dotimes (i 100)
       (sleep (random 0.1d0))
       (interrupt-thread c func))
-    (format t "~&waiting for interrupts to arrive~%")
     (loop until (= *interrupt-count* 100) do (sleep 0.1))
-    (terminate-thread c)))
+    (terminate-thread c)
+    (wait-for-threads (list c))))
 
 (format t "~&interrupt count test done~%")
 
   (loop while (thread-alive-p interruptor-thread)))
 
 (format t "~&session lock test done~%")
+
+(sb-ext:gc :full t)
+(loop repeat 20 do
+      (wait-for-threads
+       (loop for i below 100 collect
+             (sb-thread:make-thread (lambda ()))))
+      (sb-ext:gc :full t)
+      (princ "+")
+      (force-output))
+
+(format t "~&creation test done~%")
+
+;; watch out for *current-thread* being the parent thread after exit
+(let ((thread (sb-thread:make-thread (lambda ()))))
+  (wait-for-threads (list thread))
+  (assert (null (symbol-value-in-thread 'sb-thread:*current-thread*
+                                        thread))))
+
 #|  ;; a cll post from eric marsden
 | (defun crash ()
 |   (setq *debugger-hook*
index 1cf949f..d3316d4 100644 (file)
@@ -17,4 +17,4 @@
 ;;; checkins which aren't released. (And occasionally for internal
 ;;; versions, especially for internal versions off the main CVS
 ;;; branch, it gets hairier, e.g. "0.pre7.14.flaky4.13".)
-"0.9.4"
+"0.9.4.1"