Hi all!
The following is a hurd_condition_wait replacement with pthread code.
This would work assuming I can freely replace __spin_lock and
__spin_unlock glibc functions by pthread_spin_lock and
pthread_spin_unlock, and they are compatible.
hurd_pthread_cond_wait function:
/* Just like pthread_condition_wait, but cancellable. Returns true if
cancelled. */
int
hurd_pthread_cond_wait (pthread_cond_t c, pthread_mutex_t m)
{
/* This function will be called by hurd_thread_cancel while we
are blocked in the pthread_cond_wait. We wake up all threads blocked
on C,so our thread will wake up and notice the cancellation flag. */
void cancel_me (void)
{
pthread_cond_broadcast (c);
}
struct hurd_sigstate *ss = _hurd_self_sigstate ();
int cancel;
assert (ss->intr_port == MACH_PORT_NULL); /* Sanity check for signal
bugs. */
pthread_spin_lock (&ss->lock); /* With this lock we avoid being
suspended. */
cancel = ss->cancel;
if (cancel)
/* We were cancelled before doing anything. Don't block at all. */
ss->cancel = 0;
else
{
/* Tell hurd_thread_cancel how to unblock us. */
ss->cancel_hook = &cancel_me;
}
pthread_spin_unlock (&ss->lock);
if (cancel)
{
/* Cancelled on entry. Just leave the mutex locked. */
m = NULL;
}
else
{
/* Now unlock the mutex and block until woken. */
pthread_mutex_unlock (m);
pthread_cond_wait(c, m);
}
pthread_spin_lock (&ss->lock);
/* Clear the hook, now that we are done blocking. */
ss->cancel_hook = NULL;
/* Check the cancellation flag; we might have unblocked due to
cancellation rather than a normal condition_signal or
condition_broadcast (or we might have just happened to get cancelled
right after waking up). */
cancel |= ss->cancel;
ss->cancel = 0;
pthread_spin_unlock (&ss->lock);
if (m)
/* Reacquire the mutex and return. */
pthread_mutex_lock (m);
return cancel;
}
I think there is a race condition in this implementation.
After setting the hook function and unlocking ss->lock, we can receive
a signal broadcast before we have done a pthread_cond_wait.
I see several solutions:
a) Including pthread_cond_wait before we unlock ss->lock spinlock.
Can this have side effects with the thread suspension?
b) Add a new mutex or spin_lock to hurd_thread_cancel before calling the
hook function.
c) A different implementation using __pthread_enqueue function.
Can you advice me? I think c is the best, but not sure.
At the end there is the original hurd_condition_wait function attached.
--
Thanks,
Vicente.
/* Just like condition_wait, but cancellable. Returns true if cancelled. */
int
hurd_condition_wait (condition_t c, mutex_t m)
{
/* This function will be called by hurd_thread_cancel while we are blocked
in the condition_wait. We wake up all threads blocked on C,
so our thread will wake up and notice the cancellation flag. */
void cancel_me (void)
{
condition_broadcast (c);
}
struct hurd_sigstate *ss = _hurd_self_sigstate ();
cproc_t p = cproc_self ();
int cancel;
assert (ss->intr_port == MACH_PORT_NULL); /* Sanity check for signal bugs. */
p->state = CPROC_CONDWAIT | CPROC_SWITCHING;
/* Atomically enqueue our cproc on the condition variable's queue of
waiters, and mark our sigstate to indicate that `cancel_me' must be
called to wake us up. We must hold the sigstate lock while acquiring
the condition variable's lock and tweaking it, so that
hurd_thread_cancel can never suspend us and then deadlock in
condition_broadcast waiting for the condition variable's lock. */
spin_lock (&ss->lock);
spin_lock (&c->lock);
cancel = ss->cancel;
if (cancel)
/* We were cancelled before doing anything. Don't block at all. */
ss->cancel = 0;
else
{
/* Put us on the queue so that condition_broadcast will know to wake
us up. */
cthread_queue_enq (&c->queue, p);
/* Tell hurd_thread_cancel how to unblock us. */
ss->cancel_hook = &cancel_me;
}
spin_unlock (&c->lock);
spin_unlock (&ss->lock);
if (cancel)
{
/* Cancelled on entry. Just leave the mutex locked. */
m = NULL;
p->state = CPROC_RUNNING;
}
else
{
/* Now unlock the mutex and block until woken. */
#ifdef WAIT_DEBUG
p->waiting_for = (char *)c;
#endif /* WAIT_DEBUG */
mutex_unlock (m);
spin_lock (&p->lock);
if (p->state & CPROC_SWITCHING)
cproc_block ();
else
{
/* We were woken up someplace before reacquiring P->lock.
We can just continue on. */
p->state = CPROC_RUNNING;
spin_unlock(&p->lock);
}
#ifdef WAIT_DEBUG
p->waiting_for = (char *)0;
#endif /* WAIT_DEBUG */
}
spin_lock (&ss->lock);
/* Clear the hook, now that we are done blocking. */
ss->cancel_hook = NULL;
/* Check the cancellation flag; we might have unblocked due to
cancellation rather than a normal condition_signal or
condition_broadcast (or we might have just happened to get cancelled
right after waking up). */
cancel |= ss->cancel;
ss->cancel = 0;
spin_unlock (&ss->lock);
if (m)
/* Reacquire the mutex and return. */
mutex_lock (m);
return cancel;
}