Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 10 Mar 1999 18:04:08 -0800
From:      John Plevyak <jplevyak@inktomi.com>
To:        "Richard Seaman, Jr." <dick@tar.com>, John Plevyak <jplevyak@inktomi.com>
Cc:        hackers@FreeBSD.ORG
Subject:   Re: bug in linuxthreads for FreeBSD
Message-ID:  <19990310180408.E19442@proxydev.inktomi.com>
In-Reply-To: <19990310105944.F4440@tar.com>; from Richard Seaman, Jr. on Wed, Mar 10, 1999 at 10:59:44AM -0600
References:  <19990309172626.A7182@proxydev.inktomi.com> <19990310105944.F4440@tar.com>

next in thread | previous in thread | raw e-mail | index | archive | help
> > The patch I am using unconditionally removes the thread from the
> > queue.  This does not prevent the thread from being woken up
> > extaneously, but it does prevent the ASSERT and corruption of
> > the p_nextwaiting lists.
> 
> It might be possible to eliminate, or at least narrow the window
> for the "extraneous" wakeup.  It would require reworking the linux
> threads code more.  OTOH, it could just be left as is.  Here's
> what Butenhof's "Programming with POSIX Threads" says about 
> the pthread_cond_* wait functions:
> 
> "It is important that you test the predicate after locking the mutex
> and before waiting on the condition variable.  If a thread signals
> a condition variable while no threads are waiting, nothing happens.
> If some other thread calls pthread_cond_wait right after that,
> it will just keep waiting...."
> 
> and,
> 
> "It is equally important that you test the predicate again when the
> thread wakes up.  You should always wait for a condition variable
> in a loop, to protect against program errors, multiprocessor races,
> and spurious wakeups."

I agree for (at least for now).

I have now tested it for hours under high load and haven't
seen any problems.

Here is the complete patch.  I needed to patch up mutex_lock
in mutex.c as well, so I moved a couple things around, and added
some asserts.

john


diff -c --exclude=*.0 --exclude=*o --exclude=*a ./condvar.c ../../../linuxthreads.2/work/linuxthreads-0.71/condvar.c
*** ./condvar.c	Wed Mar 10 17:37:17 1999
--- ../../../linuxthreads.2/work/linuxthreads-0.71/condvar.c	Wed Mar 10 12:20:40 1999
***************
*** 22,29 ****
  #include "queue.h"
  #include "restart.h"
  
- static void remove_from_queue(pthread_queue * q, pthread_descr th);
- 
  int pthread_cond_init(pthread_cond_t *cond,
                        const pthread_condattr_t *cond_attr)
  {
--- 22,27 ----
***************
*** 51,56 ****
--- 49,55 ----
    release(&cond->c_spinlock);
    pthread_mutex_unlock(mutex);
    suspend_with_cancellation(self);
+   assert(self->p_nextwaiting == NULL && cond->c_waiting.head != self);
    pthread_mutex_lock(mutex);
    /* This is a cancellation point */
    if (self->p_canceled && self->p_cancelstate == PTHREAD_CANCEL_ENABLE) {
***************
*** 107,159 ****
                     or the timeout occurred (retsleep == 0)
                     or another interrupt occurred (retsleep == -1) */
    /* Re-acquire the spinlock */
-   acquire(&cond->c_spinlock);
    /* This is a cancellation point */
!   if (self->p_canceled && self->p_cancelstate == PTHREAD_CANCEL_ENABLE) {
      remove_from_queue(&cond->c_waiting, self);
!     release(&cond->c_spinlock);
!     pthread_mutex_lock(mutex);
      pthread_exit(PTHREAD_CANCELED);
-   }
    /* If not signaled: also remove ourselves and return an error code */
!   if (self->p_signal == 0) {
!     remove_from_queue(&cond->c_waiting, self);
!     release(&cond->c_spinlock);
!     pthread_mutex_lock(mutex);
      return retsleep == 0 ? ETIMEDOUT : EINTR;
-   }
  #else
    acquire(&cond->c_spinlock);
    enqueue(&cond->c_waiting, self);
    release(&cond->c_spinlock);
    pthread_mutex_unlock(mutex);
    retsleep = 0;
!   if (!(self->p_canceled && self->p_cancelstate == PTHREAD_CANCEL_ENABLE)) {
      /* We really should make thr_sleep return EINTR too.  It just
         returns EAGAIN if it timed out, or 0 if awakened (or
         EINVAL if bad parameter.
      */
       retsleep = syscall(SYS_thr_sleep, reltime);
-   }
  
    acquire(&cond->c_spinlock);
!   if (self->p_canceled && self->p_cancelstate == PTHREAD_CANCEL_ENABLE) {
      remove_from_queue(&cond->c_waiting, self);
!     release(&cond->c_spinlock);
!     pthread_mutex_lock(mutex);
      pthread_exit(PTHREAD_CANCELED);
!   }
!   if (retsleep) {
!     remove_from_queue(&cond->c_waiting, self);
!     release(&cond->c_spinlock);
!     pthread_mutex_lock(mutex);
      return retsleep == EAGAIN ? ETIMEDOUT : EINTR;
-   }
- 
  #endif
-   /* Otherwise, return normally */
-   release(&cond->c_spinlock);
-   pthread_mutex_lock(mutex);
    return 0;
  }
  
--- 106,145 ----
                     or the timeout occurred (retsleep == 0)
                     or another interrupt occurred (retsleep == -1) */
    /* Re-acquire the spinlock */
    /* This is a cancellation point */
!   acquire(&cond->c_spinlock);
!   if (self->p_nextwaiting != NULL || cond->c_waiting.head == self)
      remove_from_queue(&cond->c_waiting, self);
!   release(&cond->c_spinlock);
!   pthread_mutex_lock(mutex);
!   if (self->p_canceled && self->p_cancelstate == PTHREAD_CANCEL_ENABLE)
      pthread_exit(PTHREAD_CANCELED);
    /* If not signaled: also remove ourselves and return an error code */
!   if (self->p_signal == 0)
      return retsleep == 0 ? ETIMEDOUT : EINTR;
  #else
    acquire(&cond->c_spinlock);
    enqueue(&cond->c_waiting, self);
    release(&cond->c_spinlock);
    pthread_mutex_unlock(mutex);
    retsleep = 0;
!   if (!(self->p_canceled && self->p_cancelstate == PTHREAD_CANCEL_ENABLE))
      /* We really should make thr_sleep return EINTR too.  It just
         returns EAGAIN if it timed out, or 0 if awakened (or
         EINVAL if bad parameter.
      */
       retsleep = syscall(SYS_thr_sleep, reltime);
  
    acquire(&cond->c_spinlock);
!   if (self->p_nextwaiting != NULL || cond->c_waiting.head == self)
      remove_from_queue(&cond->c_waiting, self);
!   release(&cond->c_spinlock);
!   pthread_mutex_lock(mutex);
!   if (self->p_canceled && self->p_cancelstate == PTHREAD_CANCEL_ENABLE)
      pthread_exit(PTHREAD_CANCELED);
!   if (retsleep)
      return retsleep == EAGAIN ? ETIMEDOUT : EINTR;
  #endif
    return 0;
  }
  
***************
*** 210,234 ****
    return 0;
  }
  
! /* Auxiliary function on queues */
! 
! static void remove_from_queue(pthread_queue * q, pthread_descr th)
  {
    pthread_descr t;
  
!   if (q->head == NULL) return;
    if (q->head == th) {
      q->head = th->p_nextwaiting;
      if (q->head == NULL) q->tail = NULL;
      th->p_nextwaiting = NULL;
!     return;
    }
    for (t = q->head; t->p_nextwaiting != NULL; t = t->p_nextwaiting) {
      if (t->p_nextwaiting == th) {
        t->p_nextwaiting = th->p_nextwaiting;
        if (th->p_nextwaiting == NULL) q->tail = t;
        th->p_nextwaiting = NULL;
!       return;
      }
    }
  }
--- 196,219 ----
    return 0;
  }
  
! int remove_from_queue(pthread_queue * q, pthread_descr th)
  {
    pthread_descr t;
  
!   if (q->head == NULL) return 0;
    if (q->head == th) {
      q->head = th->p_nextwaiting;
      if (q->head == NULL) q->tail = NULL;
      th->p_nextwaiting = NULL;
!     return 1;
    }
    for (t = q->head; t->p_nextwaiting != NULL; t = t->p_nextwaiting) {
      if (t->p_nextwaiting == th) {
        t->p_nextwaiting = th->p_nextwaiting;
        if (th->p_nextwaiting == NULL) q->tail = t;
        th->p_nextwaiting = NULL;
!       return 1;
      }
    }
+   return 0;
  }
diff -c --exclude=*.0 --exclude=*o --exclude=*a ./mutex.c ../../../linuxthreads.2/work/linuxthreads-0.71/mutex.c
*** ./mutex.c	Wed Mar 10 17:37:17 1999
--- ../../../linuxthreads.2/work/linuxthreads-0.71/mutex.c	Wed Mar 10 12:18:22 1999
***************
*** 83,92 ****
  
  int pthread_mutex_lock(pthread_mutex_t * mutex)
  {
!   pthread_descr self;
  
    while(1) {
      acquire(&mutex->m_spinlock);
      switch(mutex->m_kind) {
      case PTHREAD_MUTEX_FAST_NP:
        if (mutex->m_count == 0) {
--- 83,94 ----
  
  int pthread_mutex_lock(pthread_mutex_t * mutex)
  {
!   pthread_descr self = thread_self();
  
    while(1) {
      acquire(&mutex->m_spinlock);
+     if (self->p_nextwaiting != NULL || mutex->m_waiting.head == self)
+       remove_from_queue(&mutex->m_waiting, self);
      switch(mutex->m_kind) {
      case PTHREAD_MUTEX_FAST_NP:
        if (mutex->m_count == 0) {
***************
*** 94,103 ****
          release(&mutex->m_spinlock);
          return 0;
        }
-       self = thread_self();
        break;
      case PTHREAD_MUTEX_RECURSIVE_NP:
-       self = thread_self();
        if (mutex->m_count == 0 || mutex->m_owner == self) {
          mutex->m_count++;
          mutex->m_owner = self;
--- 96,103 ----
***************
*** 106,112 ****
        }
        break;
     case PTHREAD_MUTEX_ERRORCHECK_NP:
-      self = thread_self();
       if (mutex->m_count == 0) {
        mutex->m_count = 1;
        mutex->m_owner = self;
--- 106,111 ----
diff -c --exclude=*.0 --exclude=*o --exclude=*a ./queue.h ../../../linuxthreads.2/work/linuxthreads-0.71/queue.h
*** ./queue.h	Fri Dec  5 09:28:21 1997
--- ../../../linuxthreads.2/work/linuxthreads-0.71/queue.h	Wed Mar 10 12:20:04 1999
***************
*** 60,62 ****
--- 60,64 ----
    }
    return th;
  }
+ 
+ int remove_from_queue(pthread_queue * q, pthread_descr th);




-- 
John Bradley Plevyak,    PhD,    jplevyak@inktomi.com,     PGP KeyID: 051130BD
Inktomi Corporation,  1900 S. Norfolk Street,  Suite 110,  San Mateo, CA 94403
W:(415)653-2830 F:(415)653-2801 P:(888)491-1332/5103192436.4911332@pagenet.net


To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-hackers" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?19990310180408.E19442>