mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2025-05-01 06:22:45 +03:00
Don't hold mutex until release cv in cv_wait
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. Also, the change reveals a race, which was unlikely before, where we call mutex_destroy while test threads are still holding the mutex. We use kthread_stop to make sure the threads are exit before mutex_destroy. Signed-off-by: Chunwei Chen <tuxoko@gmail.com> Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov> Signed-off-by: Tim Chase <tim@chase2k.com> Issue zfsonlinux/zfs#4166 Issue zfsonlinux/zfs#4106
This commit is contained in:
parent
d297a5a3a1
commit
e843553d03
@ -80,6 +80,7 @@ static void
|
|||||||
cv_wait_common(kcondvar_t *cvp, kmutex_t *mp, int state, int io)
|
cv_wait_common(kcondvar_t *cvp, kmutex_t *mp, int state, int io)
|
||||||
{
|
{
|
||||||
DEFINE_WAIT(wait);
|
DEFINE_WAIT(wait);
|
||||||
|
kmutex_t *m;
|
||||||
|
|
||||||
ASSERT(cvp);
|
ASSERT(cvp);
|
||||||
ASSERT(mp);
|
ASSERT(mp);
|
||||||
@ -87,11 +88,11 @@ cv_wait_common(kcondvar_t *cvp, kmutex_t *mp, int state, int io)
|
|||||||
ASSERT(mutex_owned(mp));
|
ASSERT(mutex_owned(mp));
|
||||||
atomic_inc(&cvp->cv_refs);
|
atomic_inc(&cvp->cv_refs);
|
||||||
|
|
||||||
if (cvp->cv_mutex == NULL)
|
m = ACCESS_ONCE(cvp->cv_mutex);
|
||||||
cvp->cv_mutex = mp;
|
if (!m)
|
||||||
|
m = xchg(&cvp->cv_mutex, mp);
|
||||||
/* Ensure the same mutex is used by all callers */
|
/* Ensure the same mutex is used by all callers */
|
||||||
ASSERT(cvp->cv_mutex == mp);
|
ASSERT(m == NULL || m == mp);
|
||||||
|
|
||||||
prepare_to_wait_exclusive(&cvp->cv_event, &wait, state);
|
prepare_to_wait_exclusive(&cvp->cv_event, &wait, state);
|
||||||
atomic_inc(&cvp->cv_waiters);
|
atomic_inc(&cvp->cv_waiters);
|
||||||
@ -106,16 +107,25 @@ cv_wait_common(kcondvar_t *cvp, kmutex_t *mp, int state, int io)
|
|||||||
io_schedule();
|
io_schedule();
|
||||||
else
|
else
|
||||||
schedule();
|
schedule();
|
||||||
mutex_enter(mp);
|
|
||||||
|
|
||||||
/* No more waiters a different mutex could be used */
|
/* No more waiters a different mutex could be used */
|
||||||
if (atomic_dec_and_test(&cvp->cv_waiters)) {
|
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;
|
cvp->cv_mutex = NULL;
|
||||||
wake_up(&cvp->cv_destroy);
|
wake_up(&cvp->cv_destroy);
|
||||||
}
|
}
|
||||||
|
|
||||||
finish_wait(&cvp->cv_event, &wait);
|
finish_wait(&cvp->cv_event, &wait);
|
||||||
atomic_dec(&cvp->cv_refs);
|
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
|
void
|
||||||
@ -148,6 +158,7 @@ __cv_timedwait_common(kcondvar_t *cvp, kmutex_t *mp, clock_t expire_time,
|
|||||||
int state)
|
int state)
|
||||||
{
|
{
|
||||||
DEFINE_WAIT(wait);
|
DEFINE_WAIT(wait);
|
||||||
|
kmutex_t *m;
|
||||||
clock_t time_left;
|
clock_t time_left;
|
||||||
|
|
||||||
ASSERT(cvp);
|
ASSERT(cvp);
|
||||||
@ -156,15 +167,16 @@ __cv_timedwait_common(kcondvar_t *cvp, kmutex_t *mp, clock_t expire_time,
|
|||||||
ASSERT(mutex_owned(mp));
|
ASSERT(mutex_owned(mp));
|
||||||
atomic_inc(&cvp->cv_refs);
|
atomic_inc(&cvp->cv_refs);
|
||||||
|
|
||||||
if (cvp->cv_mutex == NULL)
|
m = ACCESS_ONCE(cvp->cv_mutex);
|
||||||
cvp->cv_mutex = mp;
|
if (!m)
|
||||||
|
m = xchg(&cvp->cv_mutex, mp);
|
||||||
/* Ensure the same mutex is used by all callers */
|
/* Ensure the same mutex is used by all callers */
|
||||||
ASSERT(cvp->cv_mutex == mp);
|
ASSERT(m == NULL || m == mp);
|
||||||
|
|
||||||
/* XXX - Does not handle jiffie wrap properly */
|
/* XXX - Does not handle jiffie wrap properly */
|
||||||
time_left = expire_time - jiffies;
|
time_left = expire_time - jiffies;
|
||||||
if (time_left <= 0) {
|
if (time_left <= 0) {
|
||||||
|
/* XXX - doesn't reset cv_mutex */
|
||||||
atomic_dec(&cvp->cv_refs);
|
atomic_dec(&cvp->cv_refs);
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
@ -179,10 +191,13 @@ __cv_timedwait_common(kcondvar_t *cvp, kmutex_t *mp, clock_t expire_time,
|
|||||||
*/
|
*/
|
||||||
mutex_exit(mp);
|
mutex_exit(mp);
|
||||||
time_left = schedule_timeout(time_left);
|
time_left = schedule_timeout(time_left);
|
||||||
mutex_enter(mp);
|
|
||||||
|
|
||||||
/* No more waiters a different mutex could be used */
|
/* No more waiters a different mutex could be used */
|
||||||
if (atomic_dec_and_test(&cvp->cv_waiters)) {
|
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;
|
cvp->cv_mutex = NULL;
|
||||||
wake_up(&cvp->cv_destroy);
|
wake_up(&cvp->cv_destroy);
|
||||||
}
|
}
|
||||||
@ -190,6 +205,11 @@ __cv_timedwait_common(kcondvar_t *cvp, kmutex_t *mp, clock_t expire_time,
|
|||||||
finish_wait(&cvp->cv_event, &wait);
|
finish_wait(&cvp->cv_event, &wait);
|
||||||
atomic_dec(&cvp->cv_refs);
|
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);
|
return (time_left > 0 ? time_left : -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,6 +236,7 @@ __cv_timedwait_hires(kcondvar_t *cvp, kmutex_t *mp, hrtime_t expire_time,
|
|||||||
int state)
|
int state)
|
||||||
{
|
{
|
||||||
DEFINE_WAIT(wait);
|
DEFINE_WAIT(wait);
|
||||||
|
kmutex_t *m;
|
||||||
hrtime_t time_left, now;
|
hrtime_t time_left, now;
|
||||||
unsigned long time_left_us;
|
unsigned long time_left_us;
|
||||||
|
|
||||||
@ -225,11 +246,11 @@ __cv_timedwait_hires(kcondvar_t *cvp, kmutex_t *mp, hrtime_t expire_time,
|
|||||||
ASSERT(mutex_owned(mp));
|
ASSERT(mutex_owned(mp));
|
||||||
atomic_inc(&cvp->cv_refs);
|
atomic_inc(&cvp->cv_refs);
|
||||||
|
|
||||||
if (cvp->cv_mutex == NULL)
|
m = ACCESS_ONCE(cvp->cv_mutex);
|
||||||
cvp->cv_mutex = mp;
|
if (!m)
|
||||||
|
m = xchg(&cvp->cv_mutex, mp);
|
||||||
/* Ensure the same mutex is used by all callers */
|
/* Ensure the same mutex is used by all callers */
|
||||||
ASSERT(cvp->cv_mutex == mp);
|
ASSERT(m == NULL || m == mp);
|
||||||
|
|
||||||
now = gethrtime();
|
now = gethrtime();
|
||||||
time_left = expire_time - now;
|
time_left = expire_time - now;
|
||||||
@ -253,10 +274,13 @@ __cv_timedwait_hires(kcondvar_t *cvp, kmutex_t *mp, hrtime_t expire_time,
|
|||||||
* interrupts
|
* interrupts
|
||||||
*/
|
*/
|
||||||
usleep_range(time_left_us, time_left_us + 100);
|
usleep_range(time_left_us, time_left_us + 100);
|
||||||
mutex_enter(mp);
|
|
||||||
|
|
||||||
/* No more waiters a different mutex could be used */
|
/* No more waiters a different mutex could be used */
|
||||||
if (atomic_dec_and_test(&cvp->cv_waiters)) {
|
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;
|
cvp->cv_mutex = NULL;
|
||||||
wake_up(&cvp->cv_destroy);
|
wake_up(&cvp->cv_destroy);
|
||||||
}
|
}
|
||||||
@ -264,6 +288,7 @@ __cv_timedwait_hires(kcondvar_t *cvp, kmutex_t *mp, hrtime_t expire_time,
|
|||||||
finish_wait(&cvp->cv_event, &wait);
|
finish_wait(&cvp->cv_event, &wait);
|
||||||
atomic_dec(&cvp->cv_refs);
|
atomic_dec(&cvp->cv_refs);
|
||||||
|
|
||||||
|
mutex_enter(mp);
|
||||||
time_left = expire_time - gethrtime();
|
time_left = expire_time - gethrtime();
|
||||||
return (time_left > 0 ? time_left : -1);
|
return (time_left > 0 ? time_left : -1);
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,9 @@ splat_condvar_test12_thread(void *arg)
|
|||||||
ct->ct_thread->comm, atomic_read(&cv->cv_condvar.cv_waiters));
|
ct->ct_thread->comm, atomic_read(&cv->cv_condvar.cv_waiters));
|
||||||
mutex_exit(&cv->cv_mtx);
|
mutex_exit(&cv->cv_mtx);
|
||||||
|
|
||||||
|
/* wait for main thread reap us */
|
||||||
|
while (!kthread_should_stop())
|
||||||
|
schedule();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,6 +154,12 @@ splat_condvar_test1(struct file *file, void *arg)
|
|||||||
/* Wake everything for the failure case */
|
/* Wake everything for the failure case */
|
||||||
cv_broadcast(&cv.cv_condvar);
|
cv_broadcast(&cv.cv_condvar);
|
||||||
cv_destroy(&cv.cv_condvar);
|
cv_destroy(&cv.cv_condvar);
|
||||||
|
|
||||||
|
/* wait for threads to exit */
|
||||||
|
for (i = 0; i < SPLAT_CONDVAR_TEST_COUNT; i++) {
|
||||||
|
if (!IS_ERR(ct[i].ct_thread))
|
||||||
|
kthread_stop(ct[i].ct_thread);
|
||||||
|
}
|
||||||
mutex_destroy(&cv.cv_mtx);
|
mutex_destroy(&cv.cv_mtx);
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
@ -199,6 +208,12 @@ splat_condvar_test2(struct file *file, void *arg)
|
|||||||
|
|
||||||
/* Wake everything for the failure case */
|
/* Wake everything for the failure case */
|
||||||
cv_destroy(&cv.cv_condvar);
|
cv_destroy(&cv.cv_condvar);
|
||||||
|
|
||||||
|
/* wait for threads to exit */
|
||||||
|
for (i = 0; i < SPLAT_CONDVAR_TEST_COUNT; i++) {
|
||||||
|
if (!IS_ERR(ct[i].ct_thread))
|
||||||
|
kthread_stop(ct[i].ct_thread);
|
||||||
|
}
|
||||||
mutex_destroy(&cv.cv_mtx);
|
mutex_destroy(&cv.cv_mtx);
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
@ -234,6 +249,9 @@ splat_condvar_test34_thread(void *arg)
|
|||||||
|
|
||||||
mutex_exit(&cv->cv_mtx);
|
mutex_exit(&cv->cv_mtx);
|
||||||
|
|
||||||
|
/* wait for main thread reap us */
|
||||||
|
while (!kthread_should_stop())
|
||||||
|
schedule();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,6 +320,12 @@ splat_condvar_test3(struct file *file, void *arg)
|
|||||||
/* Wake everything for the failure case */
|
/* Wake everything for the failure case */
|
||||||
cv_broadcast(&cv.cv_condvar);
|
cv_broadcast(&cv.cv_condvar);
|
||||||
cv_destroy(&cv.cv_condvar);
|
cv_destroy(&cv.cv_condvar);
|
||||||
|
|
||||||
|
/* wait for threads to exit */
|
||||||
|
for (i = 0; i < SPLAT_CONDVAR_TEST_COUNT; i++) {
|
||||||
|
if (!IS_ERR(ct[i].ct_thread))
|
||||||
|
kthread_stop(ct[i].ct_thread);
|
||||||
|
}
|
||||||
mutex_destroy(&cv.cv_mtx);
|
mutex_destroy(&cv.cv_mtx);
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
@ -372,6 +396,12 @@ splat_condvar_test4(struct file *file, void *arg)
|
|||||||
/* Wake everything for the failure case */
|
/* Wake everything for the failure case */
|
||||||
cv_broadcast(&cv.cv_condvar);
|
cv_broadcast(&cv.cv_condvar);
|
||||||
cv_destroy(&cv.cv_condvar);
|
cv_destroy(&cv.cv_condvar);
|
||||||
|
|
||||||
|
/* wait for threads to exit */
|
||||||
|
for (i = 0; i < SPLAT_CONDVAR_TEST_COUNT; i++) {
|
||||||
|
if (!IS_ERR(ct[i].ct_thread))
|
||||||
|
kthread_stop(ct[i].ct_thread);
|
||||||
|
}
|
||||||
mutex_destroy(&cv.cv_mtx);
|
mutex_destroy(&cv.cv_mtx);
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
|
Loading…
Reference in New Issue
Block a user