about summary refs log tree commit diff
path: root/linuxthreads/condvar.c
diff options
context:
space:
mode:
Diffstat (limited to 'linuxthreads/condvar.c')
-rw-r--r--linuxthreads/condvar.c207
1 files changed, 207 insertions, 0 deletions
diff --git a/linuxthreads/condvar.c b/linuxthreads/condvar.c
new file mode 100644
index 0000000000..6807522edc
--- /dev/null
+++ b/linuxthreads/condvar.c
@@ -0,0 +1,207 @@
+/* Linuxthreads - a simple clone()-based implementation of Posix        */
+/* threads for Linux.                                                   */
+/* Copyright (C) 1996 Xavier Leroy (Xavier.Leroy@inria.fr)              */
+/* and Pavel Krauz (krauz@fsid.cvut.cz).                                */
+/*                                                                      */
+/* This program is free software; you can redistribute it and/or        */
+/* modify it under the terms of the GNU Library General Public License  */
+/* as published by the Free Software Foundation; either version 2       */
+/* of the License, or (at your option) any later version.               */
+/*                                                                      */
+/* This program is distributed in the hope that it will be useful,      */
+/* but WITHOUT ANY WARRANTY; without even the implied warranty of       */
+/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        */
+/* GNU Library General Public License for more details.                 */
+
+/* Condition variables */
+
+#include <errno.h>
+#include <sched.h>
+#include <stddef.h>
+#include <sys/time.h>
+#include "pthread.h"
+#include "internals.h"
+#include "spinlock.h"
+#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)
+{
+  cond->c_spinlock = 0;
+  queue_init(&cond->c_waiting);
+  return 0;
+}
+
+int pthread_cond_destroy(pthread_cond_t *cond)
+{
+  pthread_descr head;
+
+  acquire(&cond->c_spinlock);
+  head = cond->c_waiting.head;
+  release(&cond->c_spinlock);
+  if (head != NULL) return EBUSY;
+  return 0;
+}
+
+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_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);
+  }
+  return 0;
+}
+
+static inline int
+pthread_cond_timedwait_relative(pthread_cond_t *cond,
+				pthread_mutex_t *mutex,
+				const struct timespec * reltime)
+{
+  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);
+  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);
+    }
+  } 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_mutex_lock(mutex);
+    return retsleep == 0 ? ETIMEDOUT : EINTR;
+  }
+  /* Otherwise, return normally */
+  release(&cond->c_spinlock);
+  pthread_mutex_lock(mutex);
+  return 0;
+}
+
+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);
+}
+
+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);
+  return 0;
+}
+
+int pthread_cond_broadcast(pthread_cond_t *cond)
+{
+  pthread_queue tosignal;
+  pthread_descr th;
+
+  acquire(&cond->c_spinlock);
+  /* Copy the current state of the waiting queue and empty it */
+  tosignal = cond->c_waiting;
+  queue_init(&cond->c_waiting);
+  release(&cond->c_spinlock);
+  /* Now signal each process in the queue */
+  while ((th = dequeue(&tosignal)) != NULL) restart(th);
+  return 0;
+}
+
+int pthread_condattr_init(pthread_condattr_t *attr)
+{
+  return 0;
+}
+
+int pthread_condattr_destroy(pthread_condattr_t *attr)
+{
+  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;
+    }
+  }
+}