0.9.5.61:
authorGabor Melis <mega@hotpop.com>
Sat, 15 Oct 2005 08:40:36 +0000 (08:40 +0000)
committerGabor Melis <mega@hotpop.com>
Sat, 15 Oct 2005 08:40:36 +0000 (08:40 +0000)
  * threads don't inherit values of specials from their parents
    anymore because:

    1) dynamic-extent

    Suppose your package has a non-exported special, binds it,
    promises it's going to be dynamic extent and proceeds to call user
    code. The user code spawns a thread and the promise is broken.

    2) gc

    It's hard to control giving out references to objects. Yeah, it's
    similar to 1), but the colour of the smoke is different.

    3) scaling

    When starting up, a thread is given a snapshot of the parent
    thread's current values for dynamic variables. This means that the
    minimum memory required by a thread is proportional to the number
    of specials.

    1) and 2) are addressed by this change, 3) is not.

  * not having lisp objects in unstarted threads allowed undoing
    thread start vs gc recomplication and un-reinstating
    STATE_STARTING

NEWS
doc/manual/threading.texinfo
src/code/target-thread.lisp
src/runtime/thread.c
src/runtime/thread.h
src/runtime/x86-linux-os.c
version.lisp-expr

diff --git a/NEWS b/NEWS
index ae3e30c..708ab1c 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -27,6 +27,8 @@ changes in sbcl-0.9.6 relative to sbcl-0.9.5:
   * bug fix: run cleanup forms (in all threads) when receiving a SIGTERM
     and dump core on SIGQUIT
   * threads
+    ** incompatible change: threads do not inherit values of specials
+       from their parents (see manual)
     ** bug fix: threads stacks belonging to dead threads are freed by the
        next exiting thread, no need to gc to collect thread stacks anymore
     ** minor incompatible change: INTERRUPT-THREAD-ERROR-ERRNO removed
@@ -39,7 +41,7 @@ changes in sbcl-0.9.6 relative to sbcl-0.9.5:
        CONS types where some of the members have uncertain (in the
        NIL, NIL sense) type relationships to each other.
   * GENCGC
-    ** Cores produced by SAVE-LISP-AND-DIE on GENCGC platforms are 
+    ** Cores produced by SAVE-LISP-AND-DIE on GENCGC platforms are
        no longer purified unless :PURIFY T is explicitly specified.
     ** Non-purified cores are significantly smaller than before
 
index f4ef3a1..32f9ffe 100644 (file)
@@ -44,17 +44,16 @@ backports.
 @section Special Variables
 
 The interaction of special variables with multiple threads is mostly
-as one would expect, but users of other Lisps are warned that the
-behaviour of locally bound specials differs in places from what they
-may expect.
+as one would expect, with behaviour very similar to other
+implementations.
 
 @itemize
-@item 
+@item
 global special values are visible across all threads;
 @item
 bindings (e.g. using LET) are local to the thread;
 @item
-initial values in a new thread are taken from the thread that created it. 
+threads do not inherit dynamic bindings from the parent thread
 @end itemize
 
 The last point means that
@@ -65,8 +64,8 @@ The last point means that
   (sb-thread:make-thread (lambda () (print *x*))))
 @end lisp
 
-prints @code{1}.
-    
+prints @code{0} and not @code{1} as of 0.9.6.
+
 @node Mutex Support
 @comment  node-name,  next,  previous,  up
 @section Mutex Support
index 42e0530..7b80ee0 100644 (file)
@@ -14,8 +14,7 @@
 ;;; Of the WITH-PINNED-OBJECTS in this file, not every single one is
 ;;; necessary because threads are only supported with the conservative
 ;;; gencgc and numbers on the stack (returned by GET-LISP-OBJ-ADDRESS)
-;;; are treated as references. In fact, I think there isn't one that's
-;;; truly important as of now.
+;;; are treated as references.
 
 ;;; set the doc here because in early-thread FDOCUMENTATION is not
 ;;; available, yet
@@ -549,14 +548,16 @@ returns the thread exits."
                         ;; reference to this thread
                         (handle-thread-exit thread)))))))
             (values))))
-    (let ((os-thread
-           (with-pinned-objects (initial-function)
+    ;; Keep INITIAL-FUNCTION pinned until the child thread is
+    ;; initialized properly.
+    (with-pinned-objects (initial-function)
+      (let ((os-thread
              (%create-thread
-              (sb!kernel:get-lisp-obj-address initial-function)))))
-      (when (zerop os-thread)
-        (error "Can't create a new thread"))
-      (wait-on-semaphore setup-sem)
-      thread)))
+              (sb!kernel:get-lisp-obj-address initial-function))))
+        (when (zerop os-thread)
+          (error "Can't create a new thread"))
+        (wait-on-semaphore setup-sem)
+        thread))))
 
 (defun destroy-thread (thread)
   #!+sb-doc
index f881296..cf05b8d 100644 (file)
@@ -41,43 +41,6 @@ extern int linux_no_threads_p;
 
 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
- * request to other thread and waits, other thread blocks on lock. */
-void check_sig_stop_for_gc_can_arrive_or_lose()
-{
-    /* Get the current sigmask, by blocking the empty set. */
-    sigset_t empty,current;
-    sigemptyset(&empty);
-    thread_sigmask(SIG_BLOCK, &empty, &current);
-    if (sigismember(&current,SIG_STOP_FOR_GC))
-        lose("SIG_STOP_FOR_GC cannot arrive: it is blocked\n");
-    if (SymbolValue(GC_INHIBIT,arch_os_get_current_thread()) != NIL)
-        lose("SIG_STOP_FOR_GC cannot arrive: gc is inhibited\n");
-    if (arch_pseudo_atomic_atomic(NULL))
-        lose("SIG_STOP_FOR_GC cannot arrive: in pseudo atomic\n");
-}
-
-#define GET_ALL_THREADS_LOCK(name) \
-    { \
-        sigset_t _newset,_oldset; \
-        sigemptyset(&_newset); \
-        sigaddset_deferrable(&_newset); \
-        thread_sigmask(SIG_BLOCK, &_newset, &_oldset); \
-        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)); \
-        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)); \
-        pthread_mutex_unlock(&all_threads_lock); \
-        thread_sigmask(SIG_SETMASK,&_oldset,0); \
-    }
-
 #if defined(LISP_FEATURE_X86) || defined(LISP_FEATURE_X86_64)
 extern lispobj call_into_lisp_first_time(lispobj fun, lispobj *args, int nargs);
 #endif
@@ -119,7 +82,6 @@ initial_thread_trampoline(struct thread *th)
     link_thread(th);
     th->os_thread=thread_self();
     protect_control_stack_guard_page(1);
-    th->state = STATE_RUNNING;
 
 #if defined(LISP_FEATURE_X86) || defined(LISP_FEATURE_X86_64)
     return call_into_lisp_first_time(function,args,0);
@@ -180,21 +142,13 @@ new_thread_trampoline(struct thread *th)
 
     th->os_thread=thread_self();
     protect_control_stack_guard_page(1);
-    /* This thread is in STATE_STARTING so the GC is not sending it
-     * SIG_STOP_FOR_GC => no danger of deadlocking even with
-     * SIG_STOP_FOR_GC blocked. The lock is acquired in order not to
-     * enter running state with the gc running. */
+    /* Since GC can only know about this thread from the all_threads
+     * list and we're just adding this thread to it there is no danger
+     * of deadlocking even with SIG_STOP_FOR_GC blocked (which it is
+     * not). */
     pthread_mutex_lock(&all_threads_lock);
-    th->state = STATE_RUNNING;
+    link_thread(th);
     pthread_mutex_unlock(&all_threads_lock);
-    /* Now that we entered STATE_RUNNING let the gc suspend this
-     * thread. */
-    {
-        sigset_t sigset;
-        sigemptyset(&sigset);
-        sigaddset(&sigset, SIG_STOP_FOR_GC);
-        thread_sigmask(SIG_UNBLOCK, &sigset, 0);
-    }
 
     result = funcall0(function);
     th->state=STATE_DEAD;
@@ -236,6 +190,7 @@ create_thread_struct(lispobj initial_function) {
     union per_thread_data *per_thread;
     struct thread *th=0;        /*  subdue gcc */
     void *spaces=0;
+    int i;
 
     /* 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
@@ -249,14 +204,10 @@ create_thread_struct(lispobj initial_function) {
          BINDING_STACK_SIZE+
          ALIEN_STACK_SIZE);
 
-    if(all_threads) {
-        memcpy(per_thread,arch_os_get_current_thread(),
-               dynamic_values_bytes);
-    } else {
 #ifdef LISP_FEATURE_SB_THREAD
-        int i;
-        for(i=0;i<(dynamic_values_bytes/sizeof(lispobj));i++)
-            per_thread->dynamic_values[i]=NO_TLS_VALUE_MARKER_WIDETAG;
+    for(i = 0; i < (dynamic_values_bytes / sizeof(lispobj)); i++)
+        per_thread->dynamic_values[i] = NO_TLS_VALUE_MARKER_WIDETAG;
+    if (all_threads == 0) {
         if(SymbolValue(FREE_TLS_INDEX,0)==UNBOUND_MARKER_WIDETAG) {
             SetSymbolValue
                 (FREE_TLS_INDEX,
@@ -279,8 +230,8 @@ create_thread_struct(lispobj initial_function) {
         STATIC_TLS_INIT(PSEUDO_ATOMIC_INTERRUPTED,pseudo_atomic_interrupted);
 #endif
 #undef STATIC_TLS_INIT
-#endif
     }
+#endif
 
     th=&per_thread->thread;
     th->control_stack_start = spaces;
@@ -292,7 +243,7 @@ create_thread_struct(lispobj initial_function) {
     th->binding_stack_pointer=th->binding_stack_start;
     th->this=th;
     th->os_thread=0;
-    th->state=STATE_STARTING;
+    th->state=STATE_RUNNING;
 #ifdef LISP_FEATURE_STACK_GROWS_DOWNWARD_NOT_UPWARD
     th->alien_stack_pointer=((void *)th->alien_stack_start
                              + ALIEN_STACK_SIZE-N_WORD_BYTES);
@@ -370,7 +321,10 @@ boolean create_os_thread(struct thread *th,os_thread_t *kid_tid)
     sigset_t newset,oldset;
     boolean r=1;
     sigemptyset(&newset);
-    sigaddset_blockable(&newset);
+    /* Blocking deferrable signals is enough, no need to block
+     * SIG_STOP_FOR_GC because the child process is not linked onto
+     * all_threads until it's ready. */
+    sigaddset_deferrable(&newset);
     thread_sigmask(SIG_BLOCK, &newset, &oldset);
 
     if((pthread_attr_init(&attr)) ||
@@ -389,23 +343,16 @@ os_thread_t create_thread(lispobj initial_function) {
 
     if(linux_no_threads_p) return 0;
 
-    /* The new thread must be linked immediately onto all_threads for
-     * gc. */
-    GET_ALL_THREADS_LOCK("create_thread")
-    /* If it is too slow most of the allocation/initialization can
-     * be done without the lock. */
+    /* Assuming that a fresh thread struct has no lisp objects in it,
+     * linking it to all_threads can be left to the thread itself
+     * without fear of gc lossage. initial_function violates this
+     * assumption and must stay pinned until the child starts up. */
     th = create_thread_struct(initial_function);
-    if (th)
-        link_thread(th);
-    RELEASE_ALL_THREADS_LOCK("create_thread")
     if(th==0) return 0;
 
     if (create_os_thread(th,&kid_tid)) {
         return kid_tid;
     } else {
-        GET_ALL_THREADS_LOCK("create_thread")
-        unlink_thread(th);
-        RELEASE_ALL_THREADS_LOCK("create_thread")
         free_thread_struct(th);
         return 0;
     }
@@ -466,6 +413,7 @@ void gc_stop_the_world()
                   th->os_thread));
     /* stop all other threads by sending them SIG_STOP_FOR_GC */
     for(p=all_threads; p; p=p->next) {
+        gc_assert(p->os_thread != 0);
         if((p!=th) && ((p->state==STATE_RUNNING))) {
             FSHOW_SIGNAL((stderr,"/gc_stop_the_world: suspending %lu\n",
                           p->os_thread));
@@ -483,7 +431,6 @@ void gc_stop_the_world()
     /* wait for the running threads to stop or finish */
     for(p=all_threads;p;) {
         if((p!=th) && (p->state==STATE_RUNNING)) {
-            gc_assert(p->os_thread!=0);
             sched_yield();
         } else {
             p=p->next;
@@ -502,8 +449,8 @@ void gc_start_the_world()
      * restarting */
     FSHOW_SIGNAL((stderr,"/gc_start_the_world:begin\n"));
     for(p=all_threads;p;p=p->next) {
-        if((p!=th) && (p->state!=STATE_STARTING) && (p->state!=STATE_DEAD)) {
-            gc_assert(p->os_thread!=0);
+        gc_assert(p->os_thread!=0);
+        if((p!=th) && (p->state!=STATE_DEAD)) {
             if(p->state!=STATE_SUSPENDED) {
                 lose("gc_start_the_world: wrong thread state is %d\n",
                      fixnum_value(p->state));
index 2815abd..78367d4 100644 (file)
@@ -18,7 +18,6 @@ struct alloc_region { };
 #include "genesis/static-symbols.h"
 #include "genesis/thread.h"
 
-#define STATE_STARTING (make_fixnum(0))
 #define STATE_RUNNING (make_fixnum(1))
 #define STATE_SUSPENDED (make_fixnum(2))
 #define STATE_DEAD (make_fixnum(3))
index 9321e6c..d68e964 100644 (file)
@@ -81,7 +81,6 @@ int arch_os_thread_init(struct thread *thread) {
         1, MODIFY_LDT_CONTENTS_DATA, 0, 0, 0, 1
     };
     int n;
-    check_blockables_blocked_or_lose();
     thread_mutex_lock(&modify_ldt_lock);
     n=modify_ldt(0,local_ldt_copy,sizeof local_ldt_copy);
     /* get next free ldt entry */
@@ -140,7 +139,6 @@ int arch_os_thread_cleanup(struct thread *thread) {
     };
     int result;
 
-    check_blockables_blocked_or_lose();
     ldt_entry.entry_number=thread->tls_cookie;
     thread_mutex_lock(&modify_ldt_lock);
     result = modify_ldt(1, &ldt_entry, sizeof (ldt_entry));
index 70146ff..83c7b68 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.5.60"
+"0.9.5.61"