(pthread_cond_init, pthread_cond_destroy,
[kopensolaris-gnu/glibc.git] / linuxthreads / condvar.c
index 6807522..a40ae49 100644 (file)
 #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)
+{
+  __pthread_init_lock(&cond->__c_lock);
+  cond->__c_waiting = NULL;
+  return 0;
+}
+strong_alias (__pthread_cond_init, pthread_cond_init)
 
-int pthread_cond_init(pthread_cond_t *cond,
-                      const pthread_condattr_t *cond_attr)
+int __pthread_cond_destroy(pthread_cond_t *cond)
 {
-  cond->c_spinlock = 0;
-  queue_init(&cond->c_waiting);
+  if (cond->__c_waiting != NULL) return EBUSY;
   return 0;
 }
+strong_alias (__pthread_cond_destroy, pthread_cond_destroy)
+
+/* Function called by pthread_cancel to remove the thread from
+   waiting on a condition variable queue. */
 
-int pthread_cond_destroy(pthread_cond_t *cond)
+static int cond_extricate_func(void *obj, pthread_descr th)
 {
-  pthread_descr head;
+  volatile pthread_descr self = thread_self();
+  pthread_cond_t *cond = obj;
+  int did_remove = 0;
 
-  acquire(&cond->c_spinlock);
-  head = cond->c_waiting.head;
-  release(&cond->c_spinlock);
-  if (head != NULL) return EBUSY;
-  return 0;
+  __pthread_lock(&cond->__c_lock, self);
+  did_remove = remove_from_queue(&cond->__c_waiting, th);
+  __pthread_unlock(&cond->__c_lock);
+
+  return did_remove;
 }
 
-int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
+int __pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
 {
   volatile pthread_descr self = thread_self();
-  acquire(&cond->c_spinlock);
-  enqueue(&cond->c_waiting, self);
-  release(&cond->c_spinlock);
+  pthread_extricate_if extr;
+  int already_canceled = 0;
+  int spurious_wakeup_count;
+
+  /* Check whether the mutex is locked and owned by this thread.  */
+  if (mutex->__m_kind != PTHREAD_MUTEX_TIMED_NP
+      && mutex->__m_kind != PTHREAD_MUTEX_ADAPTIVE_NP
+      && mutex->__m_owner != self)
+    return EINVAL;
+
+  /* Set up extrication interface */
+  extr.pu_object = cond;
+  extr.pu_extricate_func = cond_extricate_func;
+
+  /* Register extrication interface */
+  THREAD_SETMEM(self, p_condvar_avail, 0);
+  __pthread_set_own_extricate_if(self, &extr);
+
+  /* Atomically enqueue thread for waiting, but only if it is not
+     canceled. If the thread is canceled, then it will fall through the
+     suspend call below, and then call pthread_exit without
+     having to worry about whether it is still on the condition variable queue.
+     This depends on pthread_cancel setting p_canceled before calling the
+     extricate function. */
+
+  __pthread_lock(&cond->__c_lock, self);
+  if (!(THREAD_GETMEM(self, p_canceled)
+      && THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE))
+    enqueue(&cond->__c_waiting, self);
+  else
+    already_canceled = 1;
+  __pthread_unlock(&cond->__c_lock);
+
+  if (already_canceled) {
+    __pthread_set_own_extricate_if(self, 0);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
+  }
+
   pthread_mutex_unlock(mutex);
-  suspend_with_cancellation(self);
-  pthread_mutex_lock(mutex);
-  /* This is a cancellation point */
-  if (self->p_canceled && self->p_cancelstate == PTHREAD_CANCEL_ENABLE) {
-    /* Remove ourselves from the waiting queue if we're still on it */
-    acquire(&cond->c_spinlock);
-    remove_from_queue(&cond->c_waiting, self);
-    release(&cond->c_spinlock);
-    pthread_exit(PTHREAD_CANCELED);
+
+  spurious_wakeup_count = 0;
+  while (1)
+    {
+      suspend(self);
+      if (THREAD_GETMEM(self, p_condvar_avail) == 0
+         && (THREAD_GETMEM(self, p_woken_by_cancel) == 0
+             || THREAD_GETMEM(self, p_cancelstate) != PTHREAD_CANCEL_ENABLE))
+       {
+         /* Count resumes that don't belong to us. */
+         spurious_wakeup_count++;
+         continue;
+       }
+      break;
+    }
+
+  __pthread_set_own_extricate_if(self, 0);
+
+  /* Check for cancellation again, to provide correct cancellation
+     point behavior */
+
+  if (THREAD_GETMEM(self, p_woken_by_cancel)
+      && THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE) {
+    THREAD_SETMEM(self, p_woken_by_cancel, 0);
+    pthread_mutex_lock(mutex);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
   }
+
+  /* Put back any resumes we caught that don't belong to us. */
+  while (spurious_wakeup_count--)
+    restart(self);
+
+  pthread_mutex_lock(mutex);
   return 0;
 }
+strong_alias (__pthread_cond_wait, pthread_cond_wait)
 
-static inline int
+static int
 pthread_cond_timedwait_relative(pthread_cond_t *cond,
                                pthread_mutex_t *mutex,
-                               const struct timespec * reltime)
+                               const struct timespec * abstime)
 {
   volatile pthread_descr self = thread_self();
-  sigset_t unblock, initial_mask;
-  int retsleep;
-  sigjmp_buf jmpbuf;
-
-  /* Wait on the condition */
-  acquire(&cond->c_spinlock);
-  enqueue(&cond->c_waiting, self);
-  release(&cond->c_spinlock);
+  int already_canceled = 0;
+  pthread_extricate_if extr;
+  int spurious_wakeup_count;
+
+  /* Check whether the mutex is locked and owned by this thread.  */
+  if (mutex->__m_kind != PTHREAD_MUTEX_TIMED_NP
+      && mutex->__m_kind != PTHREAD_MUTEX_ADAPTIVE_NP
+      && mutex->__m_owner != self)
+    return EINVAL;
+
+  /* Set up extrication interface */
+  extr.pu_object = cond;
+  extr.pu_extricate_func = cond_extricate_func;
+
+  /* Register extrication interface */
+  THREAD_SETMEM(self, p_condvar_avail, 0);
+  __pthread_set_own_extricate_if(self, &extr);
+
+  /* Enqueue to wait on the condition and check for cancellation. */
+  __pthread_lock(&cond->__c_lock, self);
+  if (!(THREAD_GETMEM(self, p_canceled)
+      && THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE))
+    enqueue(&cond->__c_waiting, self);
+  else
+    already_canceled = 1;
+  __pthread_unlock(&cond->__c_lock);
+
+  if (already_canceled) {
+    __pthread_set_own_extricate_if(self, 0);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
+  }
+
   pthread_mutex_unlock(mutex);
-  /* Set up a longjmp handler for the restart signal */
-  /* No need to save the signal mask, since PTHREAD_SIG_RESTART will be
-     blocked when doing the siglongjmp, and we'll just leave it blocked. */
-  if (sigsetjmp(jmpbuf, 0) == 0) {
-    self->p_signal_jmp = &jmpbuf;
-    self->p_signal = 0;
-    /* Check for cancellation */
-    if (self->p_canceled && self->p_cancelstate == PTHREAD_CANCEL_ENABLE) {
-      retsleep = -1;
-    } else {
-      /* Unblock the restart signal */
-      sigemptyset(&unblock);
-      sigaddset(&unblock, PTHREAD_SIG_RESTART);
-      sigprocmask(SIG_UNBLOCK, &unblock, &initial_mask);
-      /* Sleep for the required duration */
-      retsleep = __libc_nanosleep(reltime, NULL);
-      /* Block the restart signal again */
-      sigprocmask(SIG_SETMASK, &initial_mask, NULL);
+
+  spurious_wakeup_count = 0;
+  while (1)
+    {
+      if (!timedsuspend(self, abstime)) {
+       int was_on_queue;
+
+       /* __pthread_lock will queue back any spurious restarts that
+          may happen to it. */
+
+       __pthread_lock(&cond->__c_lock, self);
+       was_on_queue = remove_from_queue(&cond->__c_waiting, self);
+       __pthread_unlock(&cond->__c_lock);
+
+       if (was_on_queue) {
+         __pthread_set_own_extricate_if(self, 0);
+         pthread_mutex_lock(mutex);
+         return ETIMEDOUT;
+       }
+
+       /* Eat the outstanding restart() from the signaller */
+       suspend(self);
+      }
+
+      if (THREAD_GETMEM(self, p_condvar_avail) == 0
+         && (THREAD_GETMEM(self, p_woken_by_cancel) == 0
+             || THREAD_GETMEM(self, p_cancelstate) != PTHREAD_CANCEL_ENABLE))
+       {
+         /* Count resumes that don't belong to us. */
+         spurious_wakeup_count++;
+         continue;
+       }
+      break;
     }
-  } else {
-    retsleep = -1;
-  }
-  self->p_signal_jmp = NULL;
-  /* Here, either the condition was signaled (self->p_signal != 0)
-                   or we got canceled (self->p_canceled != 0)
-                   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_set_own_extricate_if(self, 0);
+
+  /* The remaining logic is the same as in other cancellable waits,
+     such as pthread_join sem_wait or pthread_cond wait. */
+
+  if (THREAD_GETMEM(self, p_woken_by_cancel)
+      && THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE) {
+    THREAD_SETMEM(self, p_woken_by_cancel, 0);
     pthread_mutex_lock(mutex);
-    return retsleep == 0 ? ETIMEDOUT : EINTR;
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
   }
-  /* Otherwise, return normally */
-  release(&cond->c_spinlock);
+
+  /* Put back any resumes we caught that don't belong to us. */
+  while (spurious_wakeup_count--)
+    restart(self);
+
   pthread_mutex_lock(mutex);
   return 0;
 }
@@ -133,75 +233,71 @@ pthread_cond_timedwait_relative(pthread_cond_t *cond,
 int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
                            const struct timespec * abstime)
 {
-  struct timeval now;
-  struct timespec reltime;
-  /* Compute a time offset relative to now */
-  __gettimeofday(&now, NULL);
-  reltime.tv_sec = abstime->tv_sec - now.tv_sec;
-  reltime.tv_nsec = abstime->tv_nsec - now.tv_usec * 1000;
-  if (reltime.tv_nsec < 0) {
-    reltime.tv_nsec += 1000000000;
-    reltime.tv_sec -= 1;
-  }
-  if (reltime.tv_sec < 0) return ETIMEDOUT;
-  return pthread_cond_timedwait_relative(cond, mutex, &reltime);
+  /* Indirect call through pointer! */
+  return pthread_cond_timedwait_relative(cond, mutex, abstime);
 }
 
-int pthread_cond_signal(pthread_cond_t *cond)
+int __pthread_cond_signal(pthread_cond_t *cond)
 {
   pthread_descr th;
 
-  acquire(&cond->c_spinlock);
-  th = dequeue(&cond->c_waiting);
-  release(&cond->c_spinlock);
-  if (th != NULL) restart(th);
+  __pthread_lock(&cond->__c_lock, NULL);
+  th = dequeue(&cond->__c_waiting);
+  __pthread_unlock(&cond->__c_lock);
+  if (th != NULL) {
+    th->p_condvar_avail = 1;
+    WRITE_MEMORY_BARRIER();
+    restart(th);
+  }
   return 0;
 }
+strong_alias (__pthread_cond_signal, pthread_cond_signal)
 
-int pthread_cond_broadcast(pthread_cond_t *cond)
+int __pthread_cond_broadcast(pthread_cond_t *cond)
 {
-  pthread_queue tosignal;
-  pthread_descr th;
+  pthread_descr tosignal, th;
 
-  acquire(&cond->c_spinlock);
+  __pthread_lock(&cond->__c_lock, NULL);
   /* Copy the current state of the waiting queue and empty it */
-  tosignal = cond->c_waiting;
-  queue_init(&cond->c_waiting);
-  release(&cond->c_spinlock);
+  tosignal = cond->__c_waiting;
+  cond->__c_waiting = NULL;
+  __pthread_unlock(&cond->__c_lock);
   /* Now signal each process in the queue */
-  while ((th = dequeue(&tosignal)) != NULL) restart(th);
+  while ((th = dequeue(&tosignal)) != NULL) {
+    th->p_condvar_avail = 1;
+    WRITE_MEMORY_BARRIER();
+    restart(th);
+  }
   return 0;
 }
+strong_alias (__pthread_cond_broadcast, pthread_cond_broadcast)
 
-int pthread_condattr_init(pthread_condattr_t *attr)
+int __pthread_condattr_init(pthread_condattr_t *attr)
 {
   return 0;
 }
+strong_alias (__pthread_condattr_init, pthread_condattr_init)
 
-int pthread_condattr_destroy(pthread_condattr_t *attr)
+int __pthread_condattr_destroy(pthread_condattr_t *attr)
 {
   return 0;
 }
+strong_alias (__pthread_condattr_destroy, pthread_condattr_destroy)
 
-/* Auxiliary function on queues */
+int pthread_condattr_getpshared (const pthread_condattr_t *attr, int *pshared)
+{
+  *pshared = PTHREAD_PROCESS_PRIVATE;
+  return 0;
+}
 
-static void remove_from_queue(pthread_queue * q, pthread_descr th)
+int pthread_condattr_setpshared (pthread_condattr_t *attr, int pshared)
 {
-  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;
-    }
-  }
+  if (pshared != PTHREAD_PROCESS_PRIVATE && pshared != PTHREAD_PROCESS_SHARED)
+    return EINVAL;
+
+  /* For now it is not possible to shared a conditional variable.  */
+  if (pshared != PTHREAD_PROCESS_PRIVATE)
+    return ENOSYS;
+
+  return 0;
 }