mirror of
https://sourceware.org/git/glibc.git
synced 2026-10-28 10:25:13 +08:00
nptl: Fix lost wake-up in robust mutex ENOTRECOVERABLE path (BZ 34724)
pthread_mutex_lock, pthread_mutex_timedlock, and pthread_mutex_trylock acquire a not recoverable robust mutex and release it with lll_unlock before returning ENOTRECOVERABLE. lll_unlock only wakes a waiter if the lock value is larger than 1, which is never true for a robust lock with FUTEX_WAITERS (bit 31) set. Since pthread_mutex_unlock wakes only one waiter when the mutex becomes not recoverable, and the woken thread acquires the lock with FUTEX_WAITERS set, the remaining waiters block forever. Release the lock as __pthread_mutex_unlock_full does, waking a waiter if FUTEX_WAITERS was set. Checked on x86_64-linux-gnu and aarch64-linux-gnu.
This commit is contained in:
1 parent
fd79535e41
commit
c8041a1ec8
5 files changed
+142
-4
No files matched your search
@@ -328,6 +328,7 @@ tests = \
|
||||
tst-pthread_exit-nothreads-static \
|
||||
tst-pthread_gettid_np \
|
||||
tst-robust-fork \
|
||||
tst-robust-notrecoverable-wake \
|
||||
tst-robust-trylock-notrecoverable \
|
||||
tst-robust12 \
|
||||
tst-robustpi1 \
|
||||
|
||||
@@ -320,7 +320,12 @@ __pthread_mutex_lock_full (pthread_mutex_t *mutex)
|
||||
/* This mutex is now not recoverable. */
|
||||
mutex->__data.__count = 0;
|
||||
int private = PTHREAD_ROBUST_MUTEX_PSHARED (mutex);
|
||||
lll_unlock (mutex->__data.__lock, private);
|
||||
/* lll_unlock cannot be used here, since it only wakes if the lock
|
||||
value is larger than 1, and a robust lock with FUTEX_WAITERS set
|
||||
is negative. */
|
||||
if ((atomic_exchange_release (&mutex->__data.__lock, 0)
|
||||
& FUTEX_WAITERS) != 0)
|
||||
futex_wake ((unsigned int *) &mutex->__data.__lock, 1, private);
|
||||
/* FIXME This violates the mutex destruction requirements. See
|
||||
__pthread_mutex_unlock_full. */
|
||||
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL);
|
||||
|
||||
@@ -257,7 +257,11 @@ __pthread_mutex_clocklock_common (pthread_mutex_t *mutex,
|
||||
/* This mutex is now not recoverable. */
|
||||
mutex->__data.__count = 0;
|
||||
int private = PTHREAD_ROBUST_MUTEX_PSHARED (mutex);
|
||||
lll_unlock (mutex->__data.__lock, private);
|
||||
/* See __pthread_mutex_lock_full for why lll_unlock cannot be used
|
||||
here. */
|
||||
if ((atomic_exchange_release (&mutex->__data.__lock, 0)
|
||||
& FUTEX_WAITERS) != 0)
|
||||
futex_wake ((unsigned int *) &mutex->__data.__lock, 1, private);
|
||||
/* FIXME This violates the mutex destruction requirements. See
|
||||
__pthread_mutex_unlock_full. */
|
||||
return __pthread_mutex_robust_error (ENOTRECOVERABLE);
|
||||
|
||||
@@ -170,8 +170,12 @@ ___pthread_mutex_trylock (pthread_mutex_t *mutex)
|
||||
{
|
||||
/* This mutex is now not recoverable. */
|
||||
mutex->__data.__count = 0;
|
||||
if (oldval == 0)
|
||||
lll_unlock (mutex->__data.__lock,
|
||||
/* See __pthread_mutex_lock_full for why lll_unlock cannot be
|
||||
used here. */
|
||||
if (oldval == 0
|
||||
&& (atomic_exchange_release (&mutex->__data.__lock, 0)
|
||||
& FUTEX_WAITERS) != 0)
|
||||
futex_wake ((unsigned int *) &mutex->__data.__lock, 1,
|
||||
PTHREAD_ROBUST_MUTEX_PSHARED (mutex));
|
||||
/* FIXME This violates the mutex destruction requirements. See
|
||||
__pthread_mutex_unlock_full. */
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/* Test that all waiters wake up when a robust mutex becomes not recoverable.
|
||||
Copyright (C) 2026 Free Software Foundation, Inc.
|
||||
This file is part of the GNU C Library.
|
||||
|
||||
The GNU C Library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
The GNU C Library 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the GNU C Library; if not, see
|
||||
<https://www.gnu.org/licenses/>. */
|
||||
|
||||
/* The owner of a robust mutex dies, the next owner gets EOWNERDEAD, and while
|
||||
it holds the mutex several other threads block on it. The owner then
|
||||
unlocks without calling pthread_mutex_consistent, so the mutex becomes not
|
||||
recoverable and every waiter has to return ENOTRECOVERABLE.
|
||||
|
||||
The unlock wakes a single waiter, which acquires the lock with
|
||||
FUTEX_WAITERS set and has to wake the next one when it releases it again. */
|
||||
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <support/check.h>
|
||||
#include <support/process_state.h>
|
||||
#include <support/timespec.h>
|
||||
#include <support/xthread.h>
|
||||
#include <support/xtime.h>
|
||||
|
||||
enum { nwaiters = 3 };
|
||||
|
||||
static pthread_mutex_t mutex;
|
||||
static bool use_timedlock;
|
||||
|
||||
static void *
|
||||
lock_and_exit (void *arg)
|
||||
{
|
||||
xpthread_mutex_lock (&mutex);
|
||||
/* Exit while holding the mutex, so that the next owner sees EOWNERDEAD. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *
|
||||
waiter (void *arg)
|
||||
{
|
||||
pid_t *tid = arg;
|
||||
__atomic_store_n (tid, gettid (), __ATOMIC_RELEASE);
|
||||
|
||||
int r;
|
||||
if (use_timedlock)
|
||||
{
|
||||
struct timespec timeout = timespec_add (xclock_now (CLOCK_REALTIME),
|
||||
make_timespec (3600, 0));
|
||||
r = pthread_mutex_timedlock (&mutex, &timeout);
|
||||
}
|
||||
else
|
||||
r = pthread_mutex_lock (&mutex);
|
||||
TEST_COMPARE (r, ENOTRECOVERABLE);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
run_test (bool pshared, bool timedlock)
|
||||
{
|
||||
printf ("info: pshared=%d timedlock=%d\n", pshared, timedlock);
|
||||
use_timedlock = timedlock;
|
||||
|
||||
pthread_mutexattr_t attr;
|
||||
xpthread_mutexattr_init (&attr);
|
||||
xpthread_mutexattr_setrobust (&attr, PTHREAD_MUTEX_ROBUST);
|
||||
if (pshared)
|
||||
xpthread_mutexattr_setpshared (&attr, PTHREAD_PROCESS_SHARED);
|
||||
xpthread_mutex_init (&mutex, &attr);
|
||||
xpthread_mutexattr_destroy (&attr);
|
||||
|
||||
xpthread_join (xpthread_create (NULL, lock_and_exit, NULL));
|
||||
TEST_COMPARE (pthread_mutex_lock (&mutex), EOWNERDEAD);
|
||||
|
||||
pid_t tids[nwaiters] = { 0 };
|
||||
pthread_t threads[nwaiters];
|
||||
for (int i = 0; i < nwaiters; i++)
|
||||
threads[i] = xpthread_create (NULL, waiter, &tids[i]);
|
||||
|
||||
/* Wait until all waiters are blocked on the mutex. A waiter that has
|
||||
published its TID does not sleep anywhere else before blocking. */
|
||||
for (int i = 0; i < nwaiters; i++)
|
||||
{
|
||||
pid_t tid;
|
||||
while ((tid = __atomic_load_n (&tids[i], __ATOMIC_ACQUIRE)) == 0)
|
||||
sched_yield ();
|
||||
support_thread_state_wait (tid, support_process_state_sleeping);
|
||||
}
|
||||
|
||||
/* Make the mutex not recoverable. */
|
||||
TEST_COMPARE (pthread_mutex_unlock (&mutex), 0);
|
||||
|
||||
/* If the wake-up is lost, the remaining waiters block forever and the
|
||||
test times out here. */
|
||||
for (int i = 0; i < nwaiters; i++)
|
||||
xpthread_join (threads[i]);
|
||||
|
||||
TEST_COMPARE (pthread_mutex_destroy (&mutex), 0);
|
||||
}
|
||||
|
||||
static int
|
||||
do_test (void)
|
||||
{
|
||||
run_test (false, false);
|
||||
run_test (false, true);
|
||||
run_test (true, false);
|
||||
run_test (true, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include <support/test-driver.c>
|
||||
Reference in new issue
Block a user