(pthread_cond_init, pthread_cond_destroy,
[kopensolaris-gnu/glibc.git] / linuxthreads / condvar.c
index a27b093..a40ae49 100644 (file)
 #include "queue.h"
 #include "restart.h"
 
-int pthread_cond_init(pthread_cond_t *cond,
-                      const pthread_condattr_t *cond_attr)
+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_destroy(pthread_cond_t *cond)
+int __pthread_cond_destroy(pthread_cond_t *cond)
 {
   if (cond->__c_waiting != NULL) return EBUSY;
   return 0;
 }
+strong_alias (__pthread_cond_destroy, pthread_cond_destroy)
 
-int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
+/* Function called by pthread_cancel to remove the thread from
+   waiting on a condition variable queue. */
+
+static int cond_extricate_func(void *obj, pthread_descr th)
 {
   volatile pthread_descr self = thread_self();
+  pthread_cond_t *cond = obj;
+  int did_remove = 0;
 
   __pthread_lock(&cond->__c_lock, self);
-  enqueue(&cond->__c_waiting, 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)
+{
+  volatile pthread_descr self = thread_self();
+  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 (THREAD_GETMEM(self, p_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) {
-    /* Remove ourselves from the waiting queue if we're still on it */
-    __pthread_lock(&cond->__c_lock, self);
-    remove_from_queue(&cond->__c_waiting, self);
-    __pthread_unlock(&cond->__c_lock);
-    pthread_exit(PTHREAD_CANCELED);
+    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;
+  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;
 
-  /* Wait on the condition */
+  /* 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);
-  enqueue(&cond->__c_waiting, 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);
- continue_waiting:
-  /* Set up a longjmp handler for the restart and cancel signals */
-  if (sigsetjmp(jmpbuf, 1) == 0) {
-    THREAD_SETMEM(self, p_signal_jmp, &jmpbuf);
-    THREAD_SETMEM(self, p_cancel_jmp, &jmpbuf);
-    THREAD_SETMEM(self, p_signal, 0);
-    /* Check for cancellation */
-    if (THREAD_GETMEM(self, p_canceled)
-       && THREAD_GETMEM(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;
-  }
-  THREAD_SETMEM(self, p_signal_jmp, NULL);
-  THREAD_SETMEM(self, p_cancel_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) */
-  /* This is a cancellation point */
-  if (THREAD_GETMEM(self, p_canceled)
+
+  __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) {
-    __pthread_lock(&cond->__c_lock, self);
-    remove_from_queue(&cond->__c_waiting, self);
-    __pthread_unlock(&cond->__c_lock);
+    THREAD_SETMEM(self, p_woken_by_cancel, 0);
     pthread_mutex_lock(mutex);
-    pthread_exit(PTHREAD_CANCELED);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
   }
-  /* If not signaled: also remove ourselves and return an error code, but
-     only if the timeout has elapsed.  If not, jsut continue waiting. */
-  if (THREAD_GETMEM(self, p_signal) == 0) {
-    if (retsleep != 0)
-      goto continue_waiting;
-    __pthread_lock(&cond->__c_lock, self);
-    remove_from_queue(&cond->__c_waiting, self);
-    __pthread_unlock(&cond->__c_lock);
-    pthread_mutex_lock(mutex);
-    return ETIMEDOUT;
-  }
-  /* Otherwise, return normally */
+
+  /* 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,32 +233,27 @@ 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;
 
   __pthread_lock(&cond->__c_lock, NULL);
   th = dequeue(&cond->__c_waiting);
   __pthread_unlock(&cond->__c_lock);
-  if (th != NULL) restart(th);
+  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_descr tosignal, th;
 
@@ -168,16 +263,41 @@ int pthread_cond_broadcast(pthread_cond_t *cond)
   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)
+{
   return 0;
 }
+strong_alias (__pthread_condattr_init, pthread_condattr_init)
 
-int pthread_condattr_init(pthread_condattr_t *attr)
+int __pthread_condattr_destroy(pthread_condattr_t *attr)
 {
   return 0;
 }
+strong_alias (__pthread_condattr_destroy, pthread_condattr_destroy)
 
-int pthread_condattr_destroy(pthread_condattr_t *attr)
+int pthread_condattr_getpshared (const pthread_condattr_t *attr, int *pshared)
 {
+  *pshared = PTHREAD_PROCESS_PRIVATE;
+  return 0;
+}
+
+int pthread_condattr_setpshared (pthread_condattr_t *attr, int pshared)
+{
+  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;
 }