Skip to content
This repository has been archived by the owner on Feb 26, 2020. It is now read-only.

Commit

Permalink
Don't hold mutex until release cv in cv_wait
Browse files Browse the repository at this point in the history
If a thread is holding mutex when doing cv_destroy, it might end up waiting a
thread in cv_wait. The waiter would wake up trying to aquire the same mutex
and cause deadlock.

We solve this by move the mutex_enter to the bottom of cv_wait, so that
the waiter will release the cv first, allowing cv_destroy to succeed and have
a chance to free the mutex.

This would create race condition on the cv_mutex. We use xchg to set and check
it to ensure we won't be harmed by the race. This would result in the cv_mutex
debugging becomes best-effort.

Signed-off-by: Chunwei Chen <tuxoko@gmail.com>
  • Loading branch information
tuxoko committed Jan 7, 2016
1 parent 2a55273 commit 2d0b95c
Showing 1 changed file with 28 additions and 10 deletions.
38 changes: 28 additions & 10 deletions module/spl/spl-condvar.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,20 @@ static void
cv_wait_common(kcondvar_t *cvp, kmutex_t *mp, int state, int io)
{
DEFINE_WAIT(wait);
kmutex_t *m;

ASSERT(cvp);
ASSERT(mp);
ASSERT(cvp->cv_magic == CV_MAGIC);
ASSERT(mutex_owned(mp));
atomic_inc(&cvp->cv_refs);

if (cvp->cv_mutex == NULL)
cvp->cv_mutex = mp;

m = ACCESS_ONCE(cvp->cv_mutex);
if (!m)
m = xchg(&cvp->cv_mutex, mp);
/* Ensure the same mutex is used by all callers */
ASSERT(cvp->cv_mutex == mp);
if (m)
ASSERT(m == mp);

prepare_to_wait_exclusive(&cvp->cv_event, &wait, state);
atomic_inc(&cvp->cv_waiters);
Expand All @@ -106,16 +108,23 @@ cv_wait_common(kcondvar_t *cvp, kmutex_t *mp, int state, int io)
io_schedule();
else
schedule();
mutex_enter(mp);

/* No more waiters a different mutex could be used */
if (atomic_dec_and_test(&cvp->cv_waiters)) {
/* This is set without any lock, so it's racy. But this is
* just for debug anyway, so make it best-effort */
cvp->cv_mutex = NULL;
wake_up(&cvp->cv_destroy);
}

finish_wait(&cvp->cv_event, &wait);
atomic_dec(&cvp->cv_refs);

/*
* Hold mutex after we release the cvp, otherwise we could dead lock
* with a thread holding the mutex and call cv_destroy.
*/
mutex_enter(mp);
}

void
Expand Down Expand Up @@ -148,6 +157,7 @@ __cv_timedwait_common(kcondvar_t *cvp, kmutex_t *mp, clock_t expire_time,
int state)
{
DEFINE_WAIT(wait);
kmutex_t *m;
clock_t time_left;

ASSERT(cvp);
Expand All @@ -156,15 +166,17 @@ __cv_timedwait_common(kcondvar_t *cvp, kmutex_t *mp, clock_t expire_time,
ASSERT(mutex_owned(mp));
atomic_inc(&cvp->cv_refs);

if (cvp->cv_mutex == NULL)
cvp->cv_mutex = mp;

m = ACCESS_ONCE(cvp->cv_mutex);
if (!m)
m = xchg(&cvp->cv_mutex, mp);
/* Ensure the same mutex is used by all callers */
ASSERT(cvp->cv_mutex == mp);
if (m)
ASSERT(m == mp);

/* XXX - Does not handle jiffie wrap properly */
time_left = expire_time - jiffies;
if (time_left <= 0) {
/* XXX - doesn't reset cv_mutex */
atomic_dec(&cvp->cv_refs);
return (-1);
}
Expand All @@ -179,17 +191,23 @@ __cv_timedwait_common(kcondvar_t *cvp, kmutex_t *mp, clock_t expire_time,
*/
mutex_exit(mp);
time_left = schedule_timeout(time_left);
mutex_enter(mp);

/* No more waiters a different mutex could be used */
if (atomic_dec_and_test(&cvp->cv_waiters)) {
/* This is set without any lock, so it's racy. But this is
* just for debug anyway, so make it best-effort */
cvp->cv_mutex = NULL;
wake_up(&cvp->cv_destroy);
}

finish_wait(&cvp->cv_event, &wait);
atomic_dec(&cvp->cv_refs);

/*
* Hold mutex after we release the cvp, otherwise we could dead lock
* with a thread holding the mutex and call cv_destroy.
*/
mutex_enter(mp);
return (time_left > 0 ? time_left : -1);
}

Expand Down

0 comments on commit 2d0b95c

Please sign in to comment.