[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH] [RFC] Fix pthread timeout handling and cancellation issues
From: |
Richard Braun |
Subject: |
[PATCH] [RFC] Fix pthread timeout handling and cancellation issues |
Date: |
Thu, 24 Jan 2013 00:14:44 +0100 |
Here is the current state of my work about fixing functions that block
for a limited time (e.g. pthread_cond_timedwait), and cancellation.
---
pthread/pt-alloc.c | 3 +
pthread/pt-cancel.c | 26 +++++-
pthread/pt-internal.h | 8 ++
pthread/pt-join.c | 2 +
pthread/pt-setcancelstate.c | 2 +
pthread/pt-setcanceltype.c | 2 +
pthread/pt-testcancel.c | 7 +-
sysdeps/generic/pt-cond-brdcast.c | 8 +-
sysdeps/generic/pt-cond-signal.c | 23 ++---
sysdeps/generic/pt-cond-timedwait.c | 147 ++++++++++++++++++++++---------
sysdeps/generic/pt-mutex-timedlock.c | 47 ++++++----
sysdeps/generic/pt-rwlock-timedrdlock.c | 49 ++++++-----
sysdeps/generic/pt-rwlock-timedwrlock.c | 50 ++++++-----
sysdeps/generic/pt-rwlock-unlock.c | 13 ++-
sysdeps/generic/sem-timedwait.c | 51 ++++++-----
sysdeps/mach/hurd/pt-docancel.c | 2 +
sysdeps/mach/pt-thread-alloc.c | 4 +
sysdeps/mach/pt-wakeup.c | 4 +-
18 files changed, 289 insertions(+), 159 deletions(-)
diff --git a/pthread/pt-alloc.c b/pthread/pt-alloc.c
index 6af2da9..89fca8a 100644
--- a/pthread/pt-alloc.c
+++ b/pthread/pt-alloc.c
@@ -55,6 +55,9 @@ initialize_pthread (struct __pthread *new, int recycling)
if (err)
return err;
+ new->cancel_lock = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
+ new->cancel_hook = NULL;
+ new->cancel_hook_arg = NULL;
new->cancel_state = PTHREAD_CANCEL_ENABLE;
new->cancel_type = PTHREAD_CANCEL_DEFERRED;
new->cancel_pending = 0;
diff --git a/pthread/pt-cancel.c b/pthread/pt-cancel.c
index d19c557..3698cce 100644
--- a/pthread/pt-cancel.c
+++ b/pthread/pt-cancel.c
@@ -31,9 +31,31 @@ pthread_cancel (pthread_t t)
if (! p)
return ESRCH;
+ __pthread_mutex_lock (&p->cancel_lock);
+ if (p->cancel_pending)
+ {
+ __pthread_mutex_unlock (&p->cancel_lock);
+ return 0;
+ }
+
p->cancel_pending = 1;
- if (p->cancel_state == PTHREAD_CANCEL_ENABLE
- && p->cancel_type == PTHREAD_CANCEL_ASYNCHRONOUS)
+
+ if (p->cancel_state != PTHREAD_CANCEL_ENABLE)
+ {
+ __pthread_mutex_unlock (&p->cancel_lock);
+ return 0;
+ }
+
+ /* Thread blocking on a cancellation point. Invoke hook to unblock.
+ See __pthread_cond_timedwait_internal. */
+ if (p->cancel_hook != NULL)
+ {
+ assert (p->cancel_type == PTHREAD_CANCEL_DEFERRED);
+ p->cancel_hook (p->cancel_hook_arg);
+ __pthread_mutex_unlock (&p->cancel_lock);
+ }
+ else if (p->cancel_type == PTHREAD_CANCEL_ASYNCHRONOUS)
+ /* CANCEL_LOCK is unlocked by this call. */
err = __pthread_do_cancel (p);
return err;
diff --git a/pthread/pt-internal.h b/pthread/pt-internal.h
index 291baf5..c98e025 100644
--- a/pthread/pt-internal.h
+++ b/pthread/pt-internal.h
@@ -73,6 +73,11 @@ struct __pthread
pthread_t thread;
/* Cancellation. */
+ pthread_mutex_t cancel_lock;
+ void (*cancel_hook)(void *); /* Called to unblock a thread blocking
+ in a cancellation point (namely,
+ __pthread_cond_timedwait_internal). */
+ void *cancel_hook_arg;
int cancel_state;
int cancel_type;
int cancel_pending;
@@ -107,6 +112,8 @@ struct __pthread
tcbhead_t *tcb;
#endif /* ENABLE_TLS */
+ /* Queue links. Since PREVP is used to determine if a thread has been
+ awaken, it must be protected by the queue lock. */
struct __pthread *next, **prevp;
};
@@ -128,6 +135,7 @@ static inline void
__pthread_dequeue (struct __pthread *thread)
{
assert (thread);
+ assert (thread->prevp);
if (thread->next)
thread->next->prevp = thread->prevp;
diff --git a/pthread/pt-join.c b/pthread/pt-join.c
index 153058b..8bd2c6c 100644
--- a/pthread/pt-join.c
+++ b/pthread/pt-join.c
@@ -40,6 +40,8 @@ pthread_join (pthread_t thread, void **status)
pthread_cleanup_push ((void (*)(void *)) __pthread_mutex_unlock,
&pthread->state_lock);
+ /* Rely on pthread_cond_wait being a cancellation point to make
+ pthread_join one too. */
while (pthread->state == PTHREAD_JOINABLE)
pthread_cond_wait (&pthread->state_cond, &pthread->state_lock);
diff --git a/pthread/pt-setcancelstate.c b/pthread/pt-setcancelstate.c
index 38550ee..7b60015 100644
--- a/pthread/pt-setcancelstate.c
+++ b/pthread/pt-setcancelstate.c
@@ -35,9 +35,11 @@ __pthread_setcancelstate (int state, int *oldstate)
break;
}
+ __pthread_mutex_lock (&p->cancel_lock);
if (oldstate)
*oldstate = p->cancel_state;
p->cancel_state = state;
+ __pthread_mutex_unlock (&p->cancel_lock);
return 0;
}
diff --git a/pthread/pt-setcanceltype.c b/pthread/pt-setcanceltype.c
index 7226a3a..3cfbe9c 100644
--- a/pthread/pt-setcanceltype.c
+++ b/pthread/pt-setcanceltype.c
@@ -35,9 +35,11 @@ __pthread_setcanceltype (int type, int *oldtype)
break;
}
+ __pthread_mutex_lock (&p->cancel_lock);
if (oldtype)
*oldtype = p->cancel_type;
p->cancel_type = type;
+ __pthread_mutex_unlock (&p->cancel_lock);
return 0;
}
diff --git a/pthread/pt-testcancel.c b/pthread/pt-testcancel.c
index 01f1ac9..3ba07b6 100644
--- a/pthread/pt-testcancel.c
+++ b/pthread/pt-testcancel.c
@@ -25,7 +25,12 @@ void
pthread_testcancel (void)
{
struct __pthread *p = _pthread_self ();
+ int cancelled;
- if (p->cancel_state == PTHREAD_CANCEL_ENABLE && p->cancel_pending)
+ __pthread_mutex_lock (&p->cancel_lock);
+ cancelled = (p->cancel_state == PTHREAD_CANCEL_ENABLE) && p->cancel_pending;
+ __pthread_mutex_unlock (&p->cancel_lock);
+
+ if (cancelled)
pthread_exit (PTHREAD_CANCELED);
}
diff --git a/sysdeps/generic/pt-cond-brdcast.c
b/sysdeps/generic/pt-cond-brdcast.c
index 999cc2d..ad44f83 100644
--- a/sysdeps/generic/pt-cond-brdcast.c
+++ b/sysdeps/generic/pt-cond-brdcast.c
@@ -28,16 +28,12 @@ __pthread_cond_broadcast (pthread_cond_t *cond)
struct __pthread *wakeup;
__pthread_spin_lock (&cond->__lock);
+ __pthread_dequeuing_iterate (cond->__queue, wakeup)
+ __pthread_wakeup (wakeup);
- wakeup = cond->__queue;
cond->__queue = NULL;
__pthread_spin_unlock (&cond->__lock);
- /* We can safely walk the list of waiting threads without holding
- the lock since it is now decoupled from the condition. */
- __pthread_dequeuing_iterate (wakeup, wakeup)
- __pthread_wakeup (wakeup);
-
return 0;
}
diff --git a/sysdeps/generic/pt-cond-signal.c b/sysdeps/generic/pt-cond-signal.c
index d7c91e6..4b5450c 100644
--- a/sysdeps/generic/pt-cond-signal.c
+++ b/sysdeps/generic/pt-cond-signal.c
@@ -21,8 +21,10 @@
#include <pt-internal.h>
-static int
-cond_signal (struct __pthread_cond *cond, int *unblocked)
+/* Unblock at least one of the threads that are blocked on condition
+ variable COND. */
+int
+__pthread_cond_signal (pthread_cond_t *cond)
{
struct __pthread *wakeup;
@@ -33,24 +35,9 @@ cond_signal (struct __pthread_cond *cond, int *unblocked)
__pthread_spin_unlock (&cond->__lock);
if (wakeup)
- {
- /* We found a thread waiting for the condition to be signalled.
- Wake it up! */
- __pthread_wakeup (wakeup);
- *unblocked = 1;
- }
+ __pthread_wakeup (wakeup);
return 0;
}
-/* Unblock at least one of the threads that are blocked on condition
- variable COND. */
-int
-__pthread_cond_signal (pthread_cond_t *cond)
-{
- int unblocked = 0;
-
- return cond_signal (cond, &unblocked);
-}
-
strong_alias (__pthread_cond_signal, pthread_cond_signal);
diff --git a/sysdeps/generic/pt-cond-timedwait.c
b/sysdeps/generic/pt-cond-timedwait.c
index 56eb1ec..978b0f4 100644
--- a/sysdeps/generic/pt-cond-timedwait.c
+++ b/sysdeps/generic/pt-cond-timedwait.c
@@ -35,6 +35,32 @@ __pthread_cond_timedwait (pthread_cond_t *cond,
strong_alias (__pthread_cond_timedwait, pthread_cond_timedwait);
+struct cancel_ctx
+ {
+ struct __pthread *wakeup;
+ pthread_cond_t *cond;
+ };
+
+static void
+cancel_hook (void *arg)
+{
+ struct cancel_ctx *ctx = arg;
+ struct __pthread *wakeup = ctx->wakeup;
+ pthread_cond_t *cond = ctx->cond;
+ int unblock;
+
+ __pthread_spin_lock (&cond->__lock);
+ /* The thread only needs to be awaken if it's blocking or about to block.
+ If it was already unblocked, it's not queued any more. */
+ unblock = wakeup->prevp != NULL;
+ if (unblock)
+ __pthread_dequeue (wakeup);
+ __pthread_spin_unlock (&cond->__lock);
+
+ if (unblock)
+ __pthread_wakeup (wakeup);
+}
+
/* Block on condition variable COND until ABSTIME. As a GNU
extension, if ABSTIME is NULL, then wait forever. MUTEX should be
held by the calling thread. On return, MUTEX will be held by the
@@ -45,67 +71,108 @@ __pthread_cond_timedwait_internal (pthread_cond_t *cond,
const struct timespec *abstime)
{
error_t err;
- int canceltype;
+ int cancelled, oldtype, drain;
clockid_t clock_id = __pthread_default_condattr.clock;
- void cleanup (void *arg)
+ if (abstime && (abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000))
+ return EINVAL;
+
+ struct __pthread *self = _pthread_self ();
+ struct cancel_ctx ctx;
+ ctx.wakeup= self;
+ ctx.cond = cond;
+
+ /* Test for a pending cancellation request, switch to deferred mode for
+ safer resource handling, and prepare the hook to call in case we're
+ cancelled while blocking. Once CANCEL_LOCK is released, the cancellation
+ hook can be called by another thread at any time. Whatever happens,
+ this function must exit with MUTEX locked.
+
+ This function contains inline implementations of pthread_testcancel and
+ pthread_setcanceltype to reduce locking overhead. */
+ __pthread_mutex_lock (&self->cancel_lock);
+ cancelled = (self->cancel_state == PTHREAD_CANCEL_ENABLE)
+ && self->cancel_pending;
+
+ if (! cancelled)
{
- struct __pthread *self = _pthread_self ();
+ self->cancel_hook = cancel_hook;
+ self->cancel_hook_arg = &ctx;
+ oldtype = self->cancel_type;
+
+ if (oldtype != PTHREAD_CANCEL_DEFERRED)
+ self->cancel_type = PTHREAD_CANCEL_DEFERRED;
+ /* Add ourselves to the list of waiters. This is done while setting
+ the cancellation hook to simplify the cancellation procedure, i.e.
+ if the thread is queued, it can be cancelled, otherwise it is
+ already unblocked, progressing on the return path. */
__pthread_spin_lock (&cond->__lock);
- if (self->prevp)
- __pthread_dequeue (self);
+ __pthread_enqueue (&cond->__queue, self);
+ if (cond->__attr)
+ clock_id = cond->__attr->clock;
__pthread_spin_unlock (&cond->__lock);
-
- pthread_setcanceltype (canceltype, &canceltype);
- __pthread_mutex_lock (mutex);
}
+ __pthread_mutex_unlock (&self->cancel_lock);
- if (abstime && (abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000))
- return EINVAL;
-
- struct __pthread *self = _pthread_self ();
-
- /* Add ourselves to the list of waiters. */
- __pthread_spin_lock (&cond->__lock);
- __pthread_enqueue (&cond->__queue, self);
- if (cond->__attr)
- clock_id = cond->__attr->clock;
- __pthread_spin_unlock (&cond->__lock);
+ if (cancelled)
+ pthread_exit (PTHREAD_CANCELED);
+ /* Release MUTEX before blocking. */
__pthread_mutex_unlock (mutex);
- /* Enter async cancelation mode. If cancelation is disabled, then
- this does not change anything which is exactly what we want. */
- pthread_cleanup_push (cleanup, 0);
- pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, &canceltype);
-
+ /* Block the thread. */
if (abstime)
+ err = __pthread_timedblock (self, abstime, clock_id);
+ else
{
- err = __pthread_timedblock (self, abstime, clock_id);
- if (err)
- /* We timed out. We may need to disconnect ourself from the
- waiter queue.
-
- FIXME: What do we do if we get a wakeup message before we
- disconnect ourself? It may remain until the next time we
- block. */
+ err = 0;
+ __pthread_block (self);
+ }
+
+ __pthread_spin_lock (&cond->__lock);
+ if (! self->prevp)
+ {
+ /* Another thread removed us from the list of waiters, which means a
+ wakeup message has been sent. It was either consumed while we were
+ blocking, or queued after we timed out and before we acquired the
+ condition lock, in which case the message queue must be drained. */
+ if (! err)
+ drain = 0;
+ else
{
assert (err == ETIMEDOUT);
-
- __pthread_spin_lock (&mutex->__lock);
- if (self->prevp)
- __pthread_dequeue (self);
- __pthread_spin_unlock (&mutex->__lock);
+ drain = 1;
}
}
else
{
- err = 0;
- __pthread_block (self);
+ /* We're still in the list of waiters. Noone attempted to wake us up,
+ i.e. we timed out. */
+ assert (err == ETIMEDOUT);
+ __pthread_dequeue (self);
+ drain = 0;
}
+ __pthread_spin_unlock (&cond->__lock);
+
+ if (drain)
+ __pthread_block (self);
+
+ /* We're almost done. Remove the unblock hook, restore the previous
+ cancellation type, and check for a pending cancellation request. */
+ __pthread_mutex_lock (&self->cancel_lock);
+ self->cancel_hook = NULL;
+ self->cancel_hook_arg = NULL;
+ self->cancel_type = oldtype;
+ cancelled = (self->cancel_state == PTHREAD_CANCEL_ENABLE)
+ && self->cancel_pending;
+ __pthread_mutex_unlock (&self->cancel_lock);
+
+ /* Reacquire MUTEX before returning/cancelling. */
+ __pthread_mutex_lock (mutex);
- pthread_cleanup_pop (1);
+ if (cancelled)
+ pthread_exit (PTHREAD_CANCELED);
return err;
}
diff --git a/sysdeps/generic/pt-mutex-timedlock.c
b/sysdeps/generic/pt-mutex-timedlock.c
index 48bffaf..43e0eda 100644
--- a/sysdeps/generic/pt-mutex-timedlock.c
+++ b/sysdeps/generic/pt-mutex-timedlock.c
@@ -30,6 +30,8 @@ int
__pthread_mutex_timedlock_internal (struct __pthread_mutex *mutex,
const struct timespec *abstime)
{
+ error_t err;
+ int drain;
struct __pthread *self;
const struct __pthread_mutexattr *attr = mutex->attr;
@@ -127,30 +129,37 @@ __pthread_mutex_timedlock_internal (struct
__pthread_mutex *mutex,
/* Block the thread. */
if (abstime)
+ err = __pthread_timedblock (self, abstime, CLOCK_REALTIME);
+ else
{
- error_t err;
-
- err = __pthread_timedblock (self, abstime, CLOCK_REALTIME);
- if (err)
- /* We timed out. We may need to disconnect ourself from the
- waiter queue.
+ err = 0;
+ __pthread_block (self);
+ }
- FIXME: What do we do if we get a wakeup message before we
- disconnect ourself? It may remain until the next time we
- block. */
- {
- assert (err == ETIMEDOUT);
+ __pthread_spin_lock (&mutex->__lock);
+ if (! self->prevp)
+ /* Another thread removed us from the queue, which means a wakeup message
+ has been sent. It was either consumed while we were blocking, or
+ queued after we timed out and before we acquired the mutex lock, in
+ which case the message queue must be drained. */
+ drain = err ? 1 : 0;
+ else
+ {
+ /* We're still in the queue. Noone attempted to wake us up, i.e. we
+ timed out. */
+ __pthread_dequeue (self);
+ drain = 0;
+ }
+ __pthread_spin_unlock (&mutex->__lock);
- __pthread_spin_lock (&mutex->__lock);
- if (self->prevp)
- __pthread_dequeue (self);
- __pthread_spin_unlock (&mutex->__lock);
+ if (drain)
+ __pthread_block (self);
- return err;
- }
+ if (err)
+ {
+ assert (err == ETIMEDOUT);
+ return err;
}
- else
- __pthread_block (self);
#if !defined(ALWAYS_TRACK_MUTEX_OWNER)
if (attr && attr->mutex_type != PTHREAD_MUTEX_NORMAL)
diff --git a/sysdeps/generic/pt-rwlock-timedrdlock.c
b/sysdeps/generic/pt-rwlock-timedrdlock.c
index a110213..a81ca71 100644
--- a/sysdeps/generic/pt-rwlock-timedrdlock.c
+++ b/sysdeps/generic/pt-rwlock-timedrdlock.c
@@ -29,6 +29,8 @@ int
__pthread_rwlock_timedrdlock_internal (struct __pthread_rwlock *rwlock,
const struct timespec *abstime)
{
+ error_t err;
+ int drain;
struct __pthread *self;
__pthread_spin_lock (&rwlock->__lock);
@@ -70,32 +72,37 @@ __pthread_rwlock_timedrdlock_internal (struct
__pthread_rwlock *rwlock,
/* Block the thread. */
if (abstime)
+ err = __pthread_timedblock (self, abstime, CLOCK_REALTIME);
+ else
{
- error_t err;
-
- err = __pthread_timedblock (self, abstime, CLOCK_REALTIME);
- if (err)
- /* We timed out. We may need to disconnect ourself from the
- waiter queue.
-
- FIXME: What do we do if we get a wakeup message before we
- disconnect ourself? It may remain until the next time we
- block. */
- {
- assert (err == ETIMEDOUT);
-
- __pthread_spin_lock (&rwlock->__lock);
- if (self->prevp)
- /* Disconnect ourself. */
- __pthread_dequeue (self);
- __pthread_spin_unlock (&rwlock->__lock);
-
- return err;
- }
+ err = 0;
+ __pthread_block (self);
}
+
+ __pthread_spin_lock (&rwlock->__lock);
+ if (! self->prevp)
+ /* Another thread removed us from the queue, which means a wakeup message
+ has been sent. It was either consumed while we were blocking, or
+ queued after we timed out and before we acquired the rwlock lock, in
+ which case the message queue must be drained. */
+ drain = err ? 1 : 0;
else
+ {
+ /* We're still in the queue. Noone attempted to wake us up, i.e. we
+ timed out. */
+ __pthread_dequeue (self);
+ drain = 0;
+ }
+ __pthread_spin_unlock (&rwlock->__lock);
+
+ if (drain)
__pthread_block (self);
+ if (err)
+ {
+ assert (err == ETIMEDOUT);
+ return err;
+ }
/* The reader count has already been increment by whoever woke us
up. */
diff --git a/sysdeps/generic/pt-rwlock-timedwrlock.c
b/sysdeps/generic/pt-rwlock-timedwrlock.c
index a5cc579..e47e936 100644
--- a/sysdeps/generic/pt-rwlock-timedwrlock.c
+++ b/sysdeps/generic/pt-rwlock-timedwrlock.c
@@ -29,6 +29,8 @@ int
__pthread_rwlock_timedwrlock_internal (struct __pthread_rwlock *rwlock,
const struct timespec *abstime)
{
+ error_t err;
+ int drain;
struct __pthread *self;
__pthread_spin_lock (&rwlock->__lock);
@@ -56,32 +58,38 @@ __pthread_rwlock_timedwrlock_internal (struct
__pthread_rwlock *rwlock,
/* Block the thread. */
if (abstime)
+ err = __pthread_timedblock (self, abstime, CLOCK_REALTIME);
+ else
{
- error_t err;
-
- err = __pthread_timedblock (self, abstime, CLOCK_REALTIME);
- if (err)
- /* We timed out. We may need to disconnect ourself from the
- waiter queue.
-
- FIXME: What do we do if we get a wakeup message before we
- disconnect ourself? It may remain until the next time we
- block. */
- {
- assert (err == ETIMEDOUT);
-
- __pthread_spin_lock (&rwlock->__lock);
- if (self->prevp)
- /* Disconnect ourself. */
- __pthread_dequeue (self);
- __pthread_spin_unlock (&rwlock->__lock);
-
- return err;
- }
+ err = 0;
+ __pthread_block (self);
}
+
+ __pthread_spin_lock (&rwlock->__lock);
+ if (! self->prevp)
+ /* Another thread removed us from the queue, which means a wakeup message
+ has been sent. It was either consumed while we were blocking, or
+ queued after we timed out and before we acquired the rwlock lock, in
+ which case the message queue must be drained. */
+ drain = err ? 1 : 0;
else
+ {
+ /* We're still in the queue. Noone attempted to wake us up, i.e. we
+ timed out. */
+ __pthread_dequeue (self);
+ drain = 0;
+ }
+ __pthread_spin_unlock (&rwlock->__lock);
+
+ if (drain)
__pthread_block (self);
+ if (err)
+ {
+ assert (err == ETIMEDOUT);
+ return err;
+ }
+
assert (rwlock->readers == 0);
return 0;
diff --git a/sysdeps/generic/pt-rwlock-unlock.c
b/sysdeps/generic/pt-rwlock-unlock.c
index fb23a0b..212cca5 100644
--- a/sysdeps/generic/pt-rwlock-unlock.c
+++ b/sysdeps/generic/pt-rwlock-unlock.c
@@ -65,19 +65,16 @@ pthread_rwlock_unlock (pthread_rwlock_t *rwlock)
if (rwlock->readerqueue)
{
- __pthread_queue_iterate (rwlock->readerqueue, wakeup)
- rwlock->readers ++;
+ __pthread_dequeuing_iterate (rwlock->readerqueue, wakeup)
+ {
+ rwlock->readers ++;
+ __pthread_wakeup (wakeup);
+ }
- wakeup = rwlock->readerqueue;
rwlock->readerqueue = 0;
__pthread_spin_unlock (&rwlock->__lock);
- /* We can safely walk the list of waiting threads without holding
- the lock since it is now decoupled from the rwlock. */
- __pthread_dequeuing_iterate (wakeup, wakeup)
- __pthread_wakeup (wakeup);
-
return 0;
}
diff --git a/sysdeps/generic/sem-timedwait.c b/sysdeps/generic/sem-timedwait.c
index 94e6dee..7ab1583 100644
--- a/sysdeps/generic/sem-timedwait.c
+++ b/sysdeps/generic/sem-timedwait.c
@@ -27,6 +27,8 @@ int
__sem_timedwait_internal (sem_t *restrict sem,
const struct timespec *restrict timeout)
{
+ error_t err;
+ int drain;
struct __pthread *self;
__pthread_spin_lock (&sem->__lock);
@@ -52,32 +54,39 @@ __sem_timedwait_internal (sem_t *restrict sem,
/* Block the thread. */
if (timeout)
+ err = __pthread_timedblock (self, timeout, CLOCK_REALTIME);
+ else
{
- error_t err;
-
- err = __pthread_timedblock (self, timeout, CLOCK_REALTIME);
- if (err)
- /* We timed out. We may need to disconnect ourself from the
- waiter queue.
-
- FIXME: What do we do if we get a wakeup message before we
- disconnect ourself? It may remain until the next time we
- block. */
- {
- assert (err == ETIMEDOUT);
-
- __pthread_spin_lock (&sem->__lock);
- if (self->prevp)
- __pthread_dequeue (self);
- __pthread_spin_unlock (&sem->__lock);
-
- errno = err;
- return -1;
- }
+ err = 0;
+ __pthread_block (self);
}
+
+ __pthread_spin_lock (&sem->__lock);
+ if (! self->prevp)
+ /* Another thread removed us from the queue, which means a wakeup message
+ has been sent. It was either consumed while we were blocking, or
+ queued after we timed out and before we acquired the semaphore lock, in
+ which case the message queue must be drained. */
+ drain = err ? 1 : 0;
else
+ {
+ /* We're still in the queue. Noone attempted to wake us up, i.e. we
+ timed out. */
+ __pthread_dequeue (self);
+ drain = 0;
+ }
+ __pthread_spin_unlock (&sem->__lock);
+
+ if (drain)
__pthread_block (self);
+ if (err)
+ {
+ assert (err == ETIMEDOUT);
+ errno = err;
+ return -1;
+ }
+
return 0;
}
diff --git a/sysdeps/mach/hurd/pt-docancel.c b/sysdeps/mach/hurd/pt-docancel.c
index 105c6fd..b3a5507 100644
--- a/sysdeps/mach/hurd/pt-docancel.c
+++ b/sysdeps/mach/hurd/pt-docancel.c
@@ -36,6 +36,8 @@ __pthread_do_cancel (struct __pthread *p)
assert (p->cancel_pending == 1);
assert (p->cancel_state == PTHREAD_CANCEL_ENABLE);
+ __pthread_mutex_unlock (&p->cancel_lock);
+
ktid = __mach_thread_self ();
me = p->kernel_thread == ktid;
__mach_port_deallocate (__mach_task_self (), ktid);
diff --git a/sysdeps/mach/pt-thread-alloc.c b/sysdeps/mach/pt-thread-alloc.c
index 3d7c046..794f63e 100644
--- a/sysdeps/mach/pt-thread-alloc.c
+++ b/sysdeps/mach/pt-thread-alloc.c
@@ -55,6 +55,10 @@ create_wakeupmsg (struct __pthread *thread)
return EAGAIN;
}
+ /* No need to queue more than one wakeup message on this port. */
+ mach_port_set_qlimit (__mach_task_self (),
+ thread->wakeupmsg.msgh_remote_port, 1);
+
return 0;
}
diff --git a/sysdeps/mach/pt-wakeup.c b/sysdeps/mach/pt-wakeup.c
index 4920d10..95fdbf9 100644
--- a/sysdeps/mach/pt-wakeup.c
+++ b/sysdeps/mach/pt-wakeup.c
@@ -31,8 +31,8 @@ __pthread_wakeup (struct __pthread *thread)
{
error_t err;
- err = __mach_msg (&thread->wakeupmsg, MACH_SEND_MSG,
+ err = __mach_msg (&thread->wakeupmsg, MACH_SEND_MSG | MACH_SEND_TIMEOUT,
sizeof (thread->wakeupmsg), 0, MACH_PORT_NULL,
- MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+ 0 , MACH_PORT_NULL);
assert_perror (err);
}
--
1.7.10.4
- [PATCH] [RFC] Fix pthread timeout handling and cancellation issues,
Richard Braun <=