glibc/4483f2500825a84382c2a6a9ac60fc77954533d7.patch

383 lines
13 KiB
Diff
Raw Normal View History

2020-09-14 19:56:04 +00:00
From 4483f2500825a84382c2a6a9ac60fc77954533d7 Mon Sep 17 00:00:00 2001
From: Mike Lothian <mike@fireburn.co.uk>
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 <pgriffais@valvesoftware.com>
Rebased-by: Mike Lothian <mike@fireburn.co.uk>
---
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 <drepper@redhat.com>, 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <not-cancel.h>
+#include "pthreadP.h"
+#include <atomic.h>
+#include <lowlevellock.h>
+#include <stap-probe.h>
+
+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 <drepper@redhat.com>, 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
+ <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <not-cancel.h>
+#include "pthreadP.h"
+#include <atomic.h>
+#include <lowlevellock.h>
+#include <stap-probe.h>
+
+/* 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