From 4483f2500825a84382c2a6a9ac60fc77954533d7 Mon Sep 17 00:00:00 2001 From: Mike Lothian Date: Tue, 4 Feb 2020 11:04:42 +0000 Subject: [PATCH] Implement pthread_mutex_lock_any() and pthread_mutex_timedlock_any(). On newer Linux kernels, futex supports a WAIT_MULTIPLE operation that can be used to implement new locking functionality that allows a given application thread to sleep while waiting for one of multiple given locks to become available. Patch-by: Pierre-Loup A. Griffais Rebased-by: Mike Lothian --- nptl/Makefile | 1 + nptl/Versions | 2 + nptl/pthreadP.h | 5 + nptl/pthread_mutex_lock_any.c | 37 ++++ nptl/pthread_mutex_timedlock_any.c | 193 ++++++++++++++++++ sysdeps/nptl/lowlevellock-futex.h | 14 ++ sysdeps/nptl/pthread.h | 10 + .../sysv/linux/x86_64/64/libpthread.abilist | 1 + 8 files changed, 263 insertions(+) create mode 100644 nptl/pthread_mutex_lock_any.c create mode 100644 nptl/pthread_mutex_timedlock_any.c diff --git a/nptl/Makefile b/nptl/Makefile index 89569c4f46..11368c93cb 100644 --- a/nptl/Makefile +++ b/nptl/Makefile @@ -97,6 +97,7 @@ libpthread-routines = nptl-init nptlfreeres vars events version pt-interp \ pthread_attr_getstack pthread_attr_setstack \ pthread_mutex_init pthread_mutex_destroy \ pthread_mutex_lock pthread_mutex_trylock \ + pthread_mutex_lock_any pthread_mutex_timedlock_any \ pthread_mutex_timedlock pthread_mutex_unlock \ pthread_mutex_cond_lock \ pthread_mutexattr_init pthread_mutexattr_destroy \ diff --git a/nptl/Versions b/nptl/Versions index aed118e717..df4a8313e2 100644 --- a/nptl/Versions +++ b/nptl/Versions @@ -285,6 +285,8 @@ libpthread { mtx_init; mtx_lock; mtx_timedlock; mtx_trylock; mtx_unlock; mtx_destroy; call_once; cnd_broadcast; cnd_destroy; cnd_init; cnd_signal; cnd_timedwait; cnd_wait; tss_create; tss_delete; tss_get; tss_set; + + pthread_mutex_lock_any; pthread_mutex_timedlock_any; } GLIBC_2.30 { diff --git a/nptl/pthreadP.h b/nptl/pthreadP.h index 6f94d6be31..87f4afff30 100644 --- a/nptl/pthreadP.h +++ b/nptl/pthreadP.h @@ -395,6 +395,11 @@ extern int __pthread_mutex_trylock (pthread_mutex_t *_mutex); extern int __pthread_mutex_lock (pthread_mutex_t *__mutex); extern int __pthread_mutex_timedlock (pthread_mutex_t *__mutex, const struct timespec *__abstime); +extern int __pthread_mutex_lock_any (pthread_mutex_t *__mutex, int mutexcount, + int *outlocked); +extern int __pthread_mutex_timedlock_any (pthread_mutex_t *__mutex, int count, + const struct timespec *__abstime, + int *outlocked); extern int __pthread_mutex_cond_lock (pthread_mutex_t *__mutex) attribute_hidden; extern void __pthread_mutex_cond_lock_adjust (pthread_mutex_t *__mutex) diff --git a/nptl/pthread_mutex_lock_any.c b/nptl/pthread_mutex_lock_any.c new file mode 100644 index 0000000000..485c213a17 --- /dev/null +++ b/nptl/pthread_mutex_lock_any.c @@ -0,0 +1,37 @@ +/* Copyright (C) 2002-2019 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper , 2002. + + 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 + . */ + +#include +#include +#include +#include +#include +#include +#include "pthreadP.h" +#include +#include +#include + +int +__pthread_mutex_lock_any (pthread_mutex_t *mutexlist, int mutexcount, + int *outlocked) +{ + return __pthread_mutex_timedlock_any(mutexlist, mutexcount, NULL, outlocked); +} + +weak_alias (__pthread_mutex_lock_any, pthread_mutex_lock_any) diff --git a/nptl/pthread_mutex_timedlock_any.c b/nptl/pthread_mutex_timedlock_any.c new file mode 100644 index 0000000000..a95687ce8e --- /dev/null +++ b/nptl/pthread_mutex_timedlock_any.c @@ -0,0 +1,193 @@ +/* Copyright (C) 2002-2019 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper , 2002. + + 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 + . */ + +#include +#include +#include +#include +#include +#include +#include "pthreadP.h" +#include +#include +#include + +/* TODO: this probably comes from a kernel header when upstream? */ +struct futex_wait_block { + int *uaddr; + int val; + int bitset; +} __attribute__((packed)); + +int +__pthread_mutex_timedlock_any (pthread_mutex_t *mutexlist, int mutexcount, + const struct timespec *abstime, int *outlocked) +{ + /* This requires futex support */ +#ifndef __NR_futex + return ENOTSUP; +#endif + + if (mutexlist == NULL) + { + /* User is asking us if kernel supports the feature. */ + + /* TODO: how does one check if supported? + * I was thinking of trying the ioctl once and then returning the static + * cached value, is that OK? + */ + return 0; + } + + if (mutexlist != NULL && mutexcount <= 0) + return EINVAL; + + if (outlocked == NULL) + return EINVAL; + + int type = PTHREAD_MUTEX_TYPE (mutexlist); + + for (int i = 1; i < mutexcount; i++) + { + /* Types have to match, since the PRIVATE flag is OP-global. */ + if (PTHREAD_MUTEX_TYPE (&mutexlist[i]) != type) + return EINVAL; + } + + int kind = type & PTHREAD_MUTEX_KIND_MASK_NP; + + /* TODO: implement recursive, errorcheck and adaptive. */ + if (kind != PTHREAD_MUTEX_NORMAL) + return EINVAL; + + /* TODO: implement robust. */ + if (type & PTHREAD_MUTEX_ROBUST_NORMAL_NP) + return EINVAL; + + /* TODO: implement PI. */ + if (type & PTHREAD_MUTEX_PRIO_INHERIT_NP) + return EINVAL; + + /* TODO: implement PP. */ + if (type & PTHREAD_MUTEX_PRIO_PROTECT_NP) + return EINVAL; + + pid_t id = THREAD_GETMEM (THREAD_SELF, tid); + int result; + + result = -1; + + for (int i = 0; i < mutexcount; i++) + { + if (__lll_trylock (&mutexlist[i].__data.__lock) == 0) + { + result = i; + break; + } + } + + while (result == -1) + { + for (int i = 0; i < mutexcount; i++) + { + int oldval = atomic_exchange_acq (&mutexlist[i].__data.__lock, 2); + + if (oldval == 0) + { + result = i; + break; + } + } + + if (result == -1) + { + /* Couldn't get one of the locks immediately, we have to sleep now. */ + struct timespec *timeout = NULL; + struct timespec rt; + + if (abstime != NULL) + { + /* Reject invalid timeouts. */ + if (abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) + return EINVAL; + + struct timeval tv; + + /* Get the current time. */ + (void) __gettimeofday (&tv, NULL); + + /* Compute relative timeout. */ + rt.tv_sec = abstime->tv_sec - tv.tv_sec; + rt.tv_nsec = abstime->tv_nsec - tv.tv_usec * 1000; + if (rt.tv_nsec < 0) + { + rt.tv_nsec += 1000000000; + --rt.tv_sec; + } + + if (rt.tv_sec < 0) + return ETIMEDOUT; + + timeout = &rt; + } + + struct futex_wait_block waitblock[mutexcount]; + + for (int i = 0; i < mutexcount; i++) + { + waitblock[i].uaddr = &mutexlist[i].__data.__lock; + waitblock[i].val = 2; + waitblock[i].bitset = ~0; + } + + long int __ret; + + /* Safe to use the flag for the first one, since all their types match. */ + int private_flag = PTHREAD_MUTEX_PSHARED (&mutexlist[0]); + + __ret = lll_futex_timed_wait_multiple (waitblock, mutexcount, timeout, + private_flag); + + if (__ret < 0) + return -__ret; /* TODO is this correct? */ + + /* Have slept, try grabbing the one that woke us up? */ + if (atomic_exchange_acq (&mutexlist[__ret].__data.__lock, 2) == 0) + { + /* We got it, done, loop will end below. */ + result = __ret; + } + } + } + + if (result != -1) + { + /* Record the ownership. */ + mutexlist[result].__data.__owner = id; + ++mutexlist[result].__data.__nusers; + + /* Let the user know which mutex is now locked. */ + *outlocked = result; + + result = 0; + } + + return result; +} + +weak_alias (__pthread_mutex_timedlock_any, pthread_mutex_timedlock_any) diff --git a/sysdeps/nptl/lowlevellock-futex.h b/sysdeps/nptl/lowlevellock-futex.h index 2209ca76a1..83f32bc8e8 100644 --- a/sysdeps/nptl/lowlevellock-futex.h +++ b/sysdeps/nptl/lowlevellock-futex.h @@ -38,6 +38,7 @@ #define FUTEX_WAKE_BITSET 10 #define FUTEX_WAIT_REQUEUE_PI 11 #define FUTEX_CMP_REQUEUE_PI 12 +#define FUTEX_WAIT_MULTIPLE 13 #define FUTEX_PRIVATE_FLAG 128 #define FUTEX_CLOCK_REALTIME 256 @@ -73,6 +74,14 @@ ? -INTERNAL_SYSCALL_ERRNO (__ret) : 0); \ }) +# define lll_futex_syscall_ret(nargs, futexp, op, ...) \ + ({ \ + long int __ret = INTERNAL_SYSCALL (futex, nargs, futexp, op, \ + __VA_ARGS__); \ + (__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (__ret)) \ + ? -INTERNAL_SYSCALL_ERRNO (__ret) : __ret); \ + }) + /* For most of these macros, the return value is never really used. Nevertheless, the protocol is that each one returns a negated errno code for failure or zero for success. (Note that the corresponding @@ -89,6 +98,11 @@ __lll_private_flag (FUTEX_WAIT, private), \ val, timeout) +# define lll_futex_timed_wait_multiple(futexp, val, timeout, private) \ + lll_futex_syscall_ret (4, futexp, \ + __lll_private_flag (FUTEX_WAIT_MULTIPLE, private), \ + val, timeout) + /* Verify whether the supplied clockid is supported by lll_futex_clock_wait_bitset. */ # define lll_futex_supported_clockid(clockid) \ diff --git a/sysdeps/nptl/pthread.h b/sysdeps/nptl/pthread.h index 8a403cbf36..f1f7332472 100644 --- a/sysdeps/nptl/pthread.h +++ b/sysdeps/nptl/pthread.h @@ -753,7 +753,17 @@ extern int pthread_mutex_trylock (pthread_mutex_t *__mutex) extern int pthread_mutex_lock (pthread_mutex_t *__mutex) __THROWNL __nonnull ((1)); +/* Lock any one of several mutexes. */ +extern int pthread_mutex_lock_any (pthread_mutex_t *__mutexlist, + int mutexcount, int *outlocked); + #ifdef __USE_XOPEN2K +/* Lock any one of several mutexes, with timeout. */ +extern int pthread_mutex_timedlock_any (pthread_mutex_t *__mutexlist, + int mutexcount, + const struct timespec *__restrict + __abstime, int *outlocked); + /* Wait until lock becomes available, or specified time passes. */ extern int pthread_mutex_timedlock (pthread_mutex_t *__restrict __mutex, const struct timespec *__restrict diff --git a/sysdeps/unix/sysv/linux/x86_64/64/libpthread.abilist b/sysdeps/unix/sysv/linux/x86_64/64/libpthread.abilist index 971269d2ef..54d966516c 100644 --- a/sysdeps/unix/sysv/linux/x86_64/64/libpthread.abilist +++ b/sysdeps/unix/sysv/linux/x86_64/64/libpthread.abilist @@ -105,6 +105,7 @@ GLIBC_2.2.5 pthread_kill_other_threads_np F GLIBC_2.2.5 pthread_mutex_destroy F GLIBC_2.2.5 pthread_mutex_init F GLIBC_2.2.5 pthread_mutex_lock F +GLIBC_2.2.5 pthread_mutex_lock_any F GLIBC_2.2.5 pthread_mutex_timedlock F GLIBC_2.2.5 pthread_mutex_trylock F GLIBC_2.2.5 pthread_mutex_unlock F