From b6537fc9d37ad800f8faba89ebbde7fdf8910d2a Mon Sep 17 00:00:00 2001 From: Cyrus Harmon Date: Mon, 20 Nov 2006 04:51:37 +0000 Subject: [PATCH] 0.9.18.62: FreeBSD threads fixes, based on the patches from NIIMI Satoshi on the sbcl-devel mailing list. This makes FreeBSD threads "work", finally, at least experimentally. * added lutex_trylock routine. * fix the sb-lutex and (not wait-p) case of get-lutex. * add pthred_mutexattr_t field to the lutex lisp object. * use libthr (1:1 threading) instead of libpthread (m:n threading) which supposedly doesn't work. * use PTHREAD_MUTEX_ERRORCHECK when attribute locking mutexes * create a dedicated cleanup thread to free threads * put a mutex around creating threads * use load_fs instead of the inline asm stubs --- src/code/target-thread.lisp | 11 +++--- src/compiler/generic/objdef.lisp | 2 ++ src/runtime/Config.x86-freebsd | 4 ++- src/runtime/pthread-lutex.c | 46 +++++++++++++++++++++++- src/runtime/thread.c | 73 ++++++++++++++++++++++++++++++++++++++ src/runtime/thread.h | 10 ++++++ src/runtime/x86-bsd-os.c | 2 +- src/runtime/x86-bsd-os.h | 5 +++ tests/threads.impure.lisp | 5 ++- version.lisp-expr | 2 +- 10 files changed, 151 insertions(+), 9 deletions(-) diff --git a/src/code/target-thread.lisp b/src/code/target-thread.lisp index ed47314..06002df 100644 --- a/src/code/target-thread.lisp +++ b/src/code/target-thread.lisp @@ -125,6 +125,9 @@ in future versions." (sb!alien:define-alien-routine ("lutex_lock" %lutex-lock) int (lutex unsigned-long)) + (sb!alien:define-alien-routine ("lutex_trylock" %lutex-trylock) + int (lutex unsigned-long)) + (sb!alien:define-alien-routine ("lutex_unlock" %lutex-unlock) int (lutex unsigned-long)) @@ -254,11 +257,11 @@ until it is available" (format *debug-io* "Thread: ~A~%" *current-thread*) (sb!debug:backtrace most-positive-fixnum *debug-io*) (force-output *debug-io*)) - ;; FIXME: sb-lutex and (not wait-p) #!+sb-lutex - (when wait-p - (with-lutex-address (lutex (mutex-lutex mutex)) - (%lutex-lock lutex)) + (when (zerop (with-lutex-address (lutex (mutex-lutex mutex)) + (if wait-p + (%lutex-lock lutex) + (%lutex-trylock lutex)))) (setf (mutex-value mutex) new-value)) #!-sb-lutex (let (old) diff --git a/src/compiler/generic/objdef.lisp b/src/compiler/generic/objdef.lisp index 7b9b242..defdafe 100644 --- a/src/compiler/generic/objdef.lisp +++ b/src/compiler/generic/objdef.lisp @@ -343,6 +343,8 @@ (prev :c-type "struct lutex *" :length 1) (mutex :c-type "pthread_mutex_t *" :length 1) + (mutexattr :c-type "pthread_mutexattr_t *" + :length 1) (condition-variable :c-type "pthread_cond_t *" :length 1)) diff --git a/src/runtime/Config.x86-freebsd b/src/runtime/Config.x86-freebsd index d3f9a27..a7533b6 100644 --- a/src/runtime/Config.x86-freebsd +++ b/src/runtime/Config.x86-freebsd @@ -19,6 +19,8 @@ ASSEM_SRC += ldso-stubs.S # runtime. LINKFLAGS += -dynamic -export-dynamic +# use libthr (1:1 threading). libpthread (m:n threading) does not work. ifdef LISP_FEATURE_SB_THREAD - OS_LIBS += -lpthread + #OS_LIBS += -lpthread + OS_LIBS += -lthr endif diff --git a/src/runtime/pthread-lutex.c b/src/runtime/pthread-lutex.c index 285891b..2da0291 100644 --- a/src/runtime/pthread-lutex.c +++ b/src/runtime/pthread-lutex.c @@ -15,6 +15,7 @@ #if defined(LISP_FEATURE_SB_THREAD) && defined(LISP_FEATURE_SB_LUTEX) +#include #include #include "runtime.h" @@ -55,10 +56,25 @@ lutex_init (tagged_lutex_t tagged_lutex) int ret; struct lutex *lutex = (struct lutex*) native_pointer(tagged_lutex); + lutex->mutexattr = malloc(sizeof(pthread_mutexattr_t)); + lutex_assert(lutex->mutexattr != 0); + + ret = pthread_mutexattr_init(lutex->mutexattr); + lutex_assert(ret == 0); + + /* The default type of mutex is implementation dependent. + * We use PTHREAD_MUTEX_ERRORCHECK so that locking on mutexes + * locked by the same thread does not cause deadlocks. */ + /* FIXME: pthread_mutexattr_settype is available on SUSv2 level + * implementations. Can be used without checking? */ + ret = pthread_mutexattr_settype(lutex->mutexattr, + PTHREAD_MUTEX_ERRORCHECK); + lutex_assert(ret == 0); + lutex->mutex = malloc(sizeof(pthread_mutex_t)); lutex_assert(lutex->mutex != 0); - ret = pthread_mutex_init(lutex->mutex, NULL); + ret = pthread_mutex_init(lutex->mutex, lutex->mutexattr); lutex_assert(ret == 0); lutex->condition_variable = malloc(sizeof(pthread_cond_t)); @@ -122,6 +138,24 @@ lutex_lock (tagged_lutex_t tagged_lutex) struct lutex *lutex = (struct lutex*) native_pointer(tagged_lutex); ret = thread_mutex_lock(lutex->mutex); + /* The mutex is locked by the same thread. */ + if (ret == EDEADLK) + return ret; + lutex_assert(ret == 0); + + return ret; +} + +int +lutex_trylock (tagged_lutex_t tagged_lutex) +{ + int ret = 0; + struct lutex *lutex = (struct lutex*) native_pointer(tagged_lutex); + + ret = pthread_mutex_trylock(lutex->mutex); + /* The mutex is locked */ + if (ret == EDEADLK || ret == EBUSY) + return ret; lutex_assert(ret == 0); return ret; @@ -134,6 +168,10 @@ lutex_unlock (tagged_lutex_t tagged_lutex) struct lutex *lutex = (struct lutex*) native_pointer(tagged_lutex); ret = thread_mutex_unlock(lutex->mutex); + /* Unlocking unlocked mutex would occur as: + * (with-mutex (mutex) (cond-wait cond mutex)) */ + if (ret == EPERM) + return ret; lutex_assert(ret == 0); return ret; @@ -156,6 +194,12 @@ lutex_destroy (tagged_lutex_t tagged_lutex) lutex->mutex = NULL; } + if (lutex->mutexattr) { + pthread_mutexattr_destroy(lutex->mutexattr); + free(lutex->mutexattr); + lutex->mutexattr = NULL; + } + return 0; } #endif diff --git a/src/runtime/thread.c b/src/runtime/thread.c index f908428..5ed27c7 100644 --- a/src/runtime/thread.c +++ b/src/runtime/thread.c @@ -51,6 +51,11 @@ #define QUEUE_FREEABLE_THREAD_STACKS #endif +#ifdef LISP_FEATURE_FREEBSD +#define CREATE_CLEANUP_THREAD +#define LOCK_CREATE_THREAD +#endif + #define ALIEN_STACK_SIZE (1*1024*1024) /* 1Mb size chosen at random */ struct freeable_stack { @@ -76,6 +81,9 @@ extern struct interrupt_data * global_interrupt_data; #ifdef LISP_FEATURE_SB_THREAD pthread_mutex_t all_threads_lock = PTHREAD_MUTEX_INITIALIZER; +#ifdef LOCK_CREATE_THREAD +static pthread_mutex_t create_thread_lock = PTHREAD_MUTEX_INITIALIZER; +#endif #endif #if defined(LISP_FEATURE_X86) || defined(LISP_FEATURE_X86_64) @@ -186,6 +194,45 @@ free_freeable_stacks() { } } +#elif defined(CREATE_CLEANUP_THREAD) +static void * +cleanup_thread(void *arg) +{ + struct freeable_stack *freeable = arg; + pthread_t self = pthread_self(); + + FSHOW((stderr, "/cleaner thread(%p): joining %p\n", + self, freeable->os_thread)); + gc_assert(pthread_join(freeable->os_thread, NULL) == 0); + FSHOW((stderr, "/cleaner thread(%p): free stack %p\n", + self, freeable->stack)); + os_invalidate(freeable->stack, THREAD_STRUCT_SIZE); + free(freeable); + + pthread_detach(self); + + return NULL; +} + +static void +create_cleanup_thread(struct thread *thread_to_be_cleaned_up) +{ + pthread_t thread; + int result; + + if (thread_to_be_cleaned_up) { + struct freeable_stack *freeable = + malloc(sizeof(struct freeable_stack)); + gc_assert(freeable != NULL); + freeable->os_thread = thread_to_be_cleaned_up->os_thread; + freeable->stack = + (os_vm_address_t) thread_to_be_cleaned_up->control_stack_start; + result = pthread_create(&thread, NULL, cleanup_thread, freeable); + gc_assert(result == 0); + sched_yield(); + } +} + #else static void free_thread_stack_later(struct thread *thread_to_be_cleaned_up) @@ -267,6 +314,8 @@ new_thread_trampoline(struct thread *th) #ifdef QUEUE_FREEABLE_THREAD_STACKS queue_freeable_thread_stack(th); +#elif defined(CREATE_CLEANUP_THREAD) + create_cleanup_thread(th); #else free_thread_stack_later(th); #endif @@ -433,6 +482,11 @@ boolean create_os_thread(struct thread *th,os_thread_t *kid_tid) FSHOW_SIGNAL((stderr,"/create_os_thread: creating new thread\n")); +#ifdef LOCK_CREATE_THREAD + retcode = pthread_mutex_lock(&create_thread_lock); + gc_assert(retcode == 0); + FSHOW_SIGNAL((stderr,"/create_os_thread: got lock\n")); +#endif sigemptyset(&newset); /* Blocking deferrable signals is enough, no need to block * SIG_STOP_FOR_GC because the child process is not linked onto @@ -466,6 +520,11 @@ boolean create_os_thread(struct thread *th,os_thread_t *kid_tid) free_freeable_stacks(); #endif thread_sigmask(SIG_SETMASK,&oldset,0); +#ifdef LOCK_CREATE_THREAD + retcode = pthread_mutex_unlock(&create_thread_lock); + gc_assert(retcode == 0); + FSHOW_SIGNAL((stderr,"/create_os_thread: released lock\n")); +#endif return r; } @@ -536,6 +595,16 @@ void gc_stop_the_world() { struct thread *p,*th=arch_os_get_current_thread(); int status, lock_ret; +#ifdef LOCK_CREATE_THREAD + /* KLUDGE: Stopping the thread during pthread_create() causes deadlock + * on FreeBSD. */ + FSHOW_SIGNAL((stderr,"/gc_stop_the_world:waiting on create_thread_lock, thread=%lu\n", + th->os_thread)); + lock_ret = pthread_mutex_lock(&create_thread_lock); + gc_assert(lock_ret == 0); + FSHOW_SIGNAL((stderr,"/gc_stop_the_world:got create_thread_lock, thread=%lu\n", + th->os_thread)); +#endif 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. */ @@ -612,6 +681,10 @@ void gc_start_the_world() lock_ret = pthread_mutex_unlock(&all_threads_lock); gc_assert(lock_ret == 0); +#ifdef LOCK_CREATE_THREAD + lock_ret = pthread_mutex_unlock(&create_thread_lock); + gc_assert(lock_ret == 0); +#endif FSHOW_SIGNAL((stderr,"/gc_start_the_world:end\n")); } diff --git a/src/runtime/thread.h b/src/runtime/thread.h index 37ad847..d124437 100644 --- a/src/runtime/thread.h +++ b/src/runtime/thread.h @@ -121,6 +121,16 @@ static inline struct thread *arch_os_get_current_thread() { sel.rpl = USER_PRIV; sel.ti = SEL_LDT; __asm__ __volatile__ ("movw %w0, %%fs" : : "r"(sel)); +#elif defined(LISP_FEATURE_FREEBSD) && defined(LISP_FEATURE_RESTORE_TLS_SEGMENT_REGISTER_FROM_TLS) + struct thread *th = pthread_getspecific(specials); + unsigned int sel = LSEL(th->tls_cookie, SEL_UPL); + unsigned int fs = rfs(); + + /* Load FS only if it's necessary. Modifying a selector + * causes privilege checking and it takes long time. */ + if (fs != sel) + load_fs(sel); + return th; #endif __asm__ __volatile__ ("movl %%fs:%c1,%0" : "=r" (me) : "i" (offsetof (struct thread,this))); diff --git a/src/runtime/x86-bsd-os.c b/src/runtime/x86-bsd-os.c index 647eb52..f1516f8 100644 --- a/src/runtime/x86-bsd-os.c +++ b/src/runtime/x86-bsd-os.c @@ -150,7 +150,7 @@ int arch_os_thread_init(struct thread *thread) { } FSHOW_SIGNAL((stderr, "/ TLS: Allocated LDT %x\n", n)); sel = LSEL(n, SEL_UPL); - __asm__ __volatile__ ("mov %0, %%fs" : : "r"(sel)); + load_fs(sel); thread->tls_cookie=n; pthread_setspecific(specials,thread); diff --git a/src/runtime/x86-bsd-os.h b/src/runtime/x86-bsd-os.h index d03b8aa..8e8b291 100644 --- a/src/runtime/x86-bsd-os.h +++ b/src/runtime/x86-bsd-os.h @@ -1,6 +1,11 @@ #ifndef _X86_BSD_OS_H #define _X86_BSD_OS_H +#ifdef LISP_FEATURE_FREEBSD +#include +#include +#endif + static inline os_context_t *arch_os_get_context(void **void_context) { return (os_context_t *) *void_context; } diff --git a/tests/threads.impure.lisp b/tests/threads.impure.lisp index 60973e0..105df1a 100644 --- a/tests/threads.impure.lisp +++ b/tests/threads.impure.lisp @@ -419,7 +419,9 @@ (force-output) (sb-ext:quit :unix-status 1))))))) -(let* ((nanosleep-errno (progn +;; (nanosleep -1 0) does not fail on FreeBSD +(let* (#-freebsd + (nanosleep-errno (progn (sb-unix:nanosleep -1 0) (sb-unix::get-errno))) (open-errno (progn @@ -428,6 +430,7 @@ (sb-unix::get-errno))) (threads (list + #-freebsd (exercise-syscall (lambda () (sb-unix:nanosleep -1 0)) nanosleep-errno) (exercise-syscall (lambda () (open "no-such-file" :if-does-not-exist nil)) diff --git a/version.lisp-expr b/version.lisp-expr index a72c501..2e2e94a 100644 --- a/version.lisp-expr +++ b/version.lisp-expr @@ -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.18.61" +"0.9.18.62" -- 1.7.10.4