nptl: futex_lock_pi deadlock detection provides valuable information but it is turned into a rather cryptic assertion failure
Checks
| Context |
Check |
Description |
| redhat-pt-bot/TryBot-apply_patch |
success
|
Patch applied to master at the time it was sent
|
| linaro-tcwg-bot/tcwg_glibc_build--master-arm |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 |
success
|
Test passed
|
| linaro-tcwg-bot/tcwg_glibc_check--master-arm |
success
|
Test passed
|
| redhat-pt-bot/TryBot-32bit |
fail
|
Patch caused testsuite regressions
|
Commit Message
Hi Adhemerval,
thanks for your assessment and please excuse the very long wait.
Getting the copyright clearance from my employer to let me submit this
patch took... a bit longer than expected. But we eventually did it.
Attached is a conservative patch that will change pthread_mutex_lock
only for recursive and error-checking PI mutexes to propagate the
EDEADLK from the kernel to the user (these mutex types would previously
have triggered the assertion). In my opinion, it would probably be more
useful and easier to explain to propagate EDEADLK for all PI mutexes.
But doing so gave me these two new test failures:
nptl/tst-mutexpi6
nptl/tst-thread-affinity-sched
Apparently, those are depending on the deadlock actually happening for
normal mutexes. Please help me understand whether this is behavior that
we'd like/have to preserve. I'll be happy to adjust the patch
accordingly.
The added test case currently only checks for EDEADLK to be returned,
but doesn't cover the cases where we still expect the deadlock to
happen. I could add these as well, but it would introduce an
(unreasonably?) long delay, waiting for the alarm clock to go off
eventually. Would you rather take this delay over the lack of test
coverage? Please also let me know whether having one test executable
that loops over the various types (current patch) or one executable per
type would be preferred.
I've tried my best to be consistent with the code formatting, using the
.clang-format file as far as this was applicable, but the space & tabs
mixture gave me some trouble.
Many thanks and best regards,
Moritz
From 618d71460ca8f66cd521df7bccc2fd16f8899335 Mon Sep 17 00:00:00 2001
From: Moritz Klammler <moritz.klammler.ext@siemens.com>
Date: Thu, 2 Apr 2026 18:10:01 +0200
Subject: [PATCH 1/1] nptl: Propagate EDEADLK from FUTEX_LOCK_PI for
errror-checking and recursive mutexes
This patch changes the behavior of pthread_mutex_lock for error-checking and
recursive PI mutexes in case of non-trivial deadlock. The user-space code
doesn't detect the case where two or more threads would mutually deadlock each
other, but the Linux kernel can. NPTL's previous behavior, if the syscall
returns EDEADLK, was to run into an assertion. With this patch, the error code
will be propagated to the caller who might then, at its own discretion and with
knowledge about the application-level logic, use it to attempt resolving the
situation gracefully or terminate the process after all.
The behavior for other (normal) mutex types is not changed, they will continue
to actually deadlock the calling thread.
Since POSIX doesn't seem to mandate any particular behavior for this situation,
and no existing code should have a dependency of running into an assertion,
changing this behavior to what is presumably the most useful one seems to be
justified.
The previous (design) discussion can be seen here:
https://sourceware.org/pipermail/libc-alpha/2025-December/173431.html
Signed-off-by: Moritz Klammler <moritz.klammler.ext@siemens.com>
---
nptl/Makefile | 1 +
nptl/pthread_mutex_lock.c | 15 +++-
nptl/tst-deadlk-pi.c | 2 +
nptl/tst-deadlk.c | 183 ++++++++++++++++++++++++++++++++++++++
4 files changed, 198 insertions(+), 3 deletions(-)
create mode 100644 nptl/tst-deadlk-pi.c
create mode 100644 nptl/tst-deadlk.c
Comments
On 02/04/26 14:09, Moritz KLAMMLER (FERCHAU) wrote:
> Hi Adhemerval,
>
> thanks for your assessment and please excuse the very long wait.
> Getting the copyright clearance from my employer to let me submit this
> patch took... a bit longer than expected. But we eventually did it.
Thanks for working on this, I looking forward for a v2.
>
> Attached is a conservative patch that will change pthread_mutex_lock
> only for recursive and error-checking PI mutexes to propagate the
> EDEADLK from the kernel to the user (these mutex types would previously
> have triggered the assertion). In my opinion, it would probably be more
> useful and easier to explain to propagate EDEADLK for all PI mutexes.
> But doing so gave me these two new test failures:
>
> nptl/tst-mutexpi6
> nptl/tst-thread-affinity-sched
>
> Apparently, those are depending on the deadlock actually happening for
> normal mutexes. Please help me understand whether this is behavior that
> we'd like/have to preserve. I'll be happy to adjust the patch
> accordingly.
The glibc defines PTHREAD_MUTEX_DEFAULT as PTHREAD_MUTEX_NORMAL:
sysdeps/nptl/pthread.h:
53 #if defined __USE_UNIX98 || defined __USE_XOPEN2K8
54 ,
55 PTHREAD_MUTEX_NORMAL = PTHREAD_MUTEX_TIMED_NP,
56 PTHREAD_MUTEX_RECURSIVE = PTHREAD_MUTEX_RECURSIVE_NP,
57 PTHREAD_MUTEX_ERRORCHECK = PTHREAD_MUTEX_ERRORCHECK_NP,
58 PTHREAD_MUTEX_DEFAULT = PTHREAD_MUTEX_NORMAL
59 #endif
60 #ifdef __USE_GNU
And PTHREAD_MUTEX_NORMAL is specified to deadlock in such cases [1].
[1] https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_mutex_lock.html
>
> The added test case currently only checks for EDEADLK to be returned,
> but doesn't cover the cases where we still expect the deadlock to
> happen. I could add these as well, but it would introduce an
> (unreasonably?) long delay, waiting for the alarm clock to go off
> eventually. Would you rather take this delay over the lack of test
> coverage? Please also let me know whether having one test executable
> that loops over the various types (current patch) or one executable per
> type would be preferred.
I would prefer the later (one less binary to build and run). For the
deadlock to happen, it would be better to spawn a new process with
support_capture_subprocess, trigger the deadlock, and wait it with
short delayed_exit value.
>
> I've tried my best to be consistent with the code formatting, using the
> .clang-format file as far as this was applicable, but the space & tabs
> mixture gave me some trouble.+
The testcase should use libsupport, as below.
>
> Many thanks and best regards,
> Moritz
>
>
> From 618d71460ca8f66cd521df7bccc2fd16f8899335 Mon Sep 17 00:00:00 2001
> From: Moritz Klammler <moritz.klammler.ext@siemens.com>
> Date: Thu, 2 Apr 2026 18:10:01 +0200
> Subject: [PATCH 1/1] nptl: Propagate EDEADLK from FUTEX_LOCK_PI for
> errror-checking and recursive mutexes
>
> This patch changes the behavior of pthread_mutex_lock for error-checking and
> recursive PI mutexes in case of non-trivial deadlock. The user-space code
> doesn't detect the case where two or more threads would mutually deadlock each
> other, but the Linux kernel can. NPTL's previous behavior, if the syscall
> returns EDEADLK, was to run into an assertion. With this patch, the error code
> will be propagated to the caller who might then, at its own discretion and with
> knowledge about the application-level logic, use it to attempt resolving the
> situation gracefully or terminate the process after all.
>
> The behavior for other (normal) mutex types is not changed, they will continue
> to actually deadlock the calling thread.
>
> Since POSIX doesn't seem to mandate any particular behavior for this situation,
> and no existing code should have a dependency of running into an assertion,
> changing this behavior to what is presumably the most useful one seems to be
> justified.
>
> The previous (design) discussion can be seen here:
> https://sourceware.org/pipermail/libc-alpha/2025-December/173431.html
>
> Signed-off-by: Moritz Klammler <moritz.klammler.ext@siemens.com>
> ---
> nptl/Makefile | 1 +
> nptl/pthread_mutex_lock.c | 15 +++-
> nptl/tst-deadlk-pi.c | 2 +
> nptl/tst-deadlk.c | 183 ++++++++++++++++++++++++++++++++++++++
> 4 files changed, 198 insertions(+), 3 deletions(-)
> create mode 100644 nptl/tst-deadlk-pi.c
> create mode 100644 nptl/tst-deadlk.c
>
> diff --git a/nptl/Makefile b/nptl/Makefile
> index 85f95dd0cf..41106677f7 100644
> --- a/nptl/Makefile
> +++ b/nptl/Makefile
> @@ -283,6 +283,7 @@ tests = \
> tst-cleanup5 \
> tst-cond26 \
> tst-context1 \
> + tst-deadlk-pi \
> tst-default-attr \
> tst-dlsym1 \
> tst-exec4 \
> diff --git a/nptl/pthread_mutex_lock.c b/nptl/pthread_mutex_lock.c
> index a697f2b6ca..faf53d44fe 100644
> --- a/nptl/pthread_mutex_lock.c
> +++ b/nptl/pthread_mutex_lock.c
> @@ -418,9 +418,18 @@ __pthread_mutex_lock_full (pthread_mutex_t *mutex)
> NULL, private);
> if (e == ESRCH || e == EDEADLK)
> {
> - assert (e != EDEADLK
> - || (kind != PTHREAD_MUTEX_ERRORCHECK_NP
> - && kind != PTHREAD_MUTEX_RECURSIVE_NP));
> + if (e == EDEADLK
> + && (kind == PTHREAD_MUTEX_ERRORCHECK_NP
> + || kind == PTHREAD_MUTEX_RECURSIVE_NP))
> + {
> + /* FUTEX_LOCK_PI may return EDEADLK due to cross‑thread
> + * deadlock detection, beyond the same‑thread recursive
> + * check above. Pass this error through for these two
> + * mutex types; otherwise, intentionally deadlock for
> + * normal mutexes. */
The usual comment format is to no use '*' as the start of new line:
/* FUTEX_LOCK_PI may return EDEADLK due to cross‑thread
deadlock detection, beyond the same‑thread recursive
[...]
> + return e;
> + }
> +
> /* ESRCH can happen only for non-robust PI mutexes where
> the owner of the lock died. */
> assert (e != ESRCH || !robust);
> diff --git a/nptl/tst-deadlk-pi.c b/nptl/tst-deadlk-pi.c
> new file mode 100644
> index 0000000000..3196a546d0
> --- /dev/null
> +++ b/nptl/tst-deadlk-pi.c
> @@ -0,0 +1,2 @@
> +#define TST_DEADLK_MUTEX_PI 1
> +#include "tst-deadlk.c"
> diff --git a/nptl/tst-deadlk.c b/nptl/tst-deadlk.c
> new file mode 100644
> index 0000000000..3a9ea8ee44
> --- /dev/null
> +++ b/nptl/tst-deadlk.c
> @@ -0,0 +1,183 @@
> +/* This test checks behavior not required by POSIX. */
> +/* https://sourceware.org/pipermail/libc-alpha/2025-December/173431.html */
This need a Copyright header, along with a one-line description (first line)
of what tests intendes.
> +
> +#include <errno.h>
> +#include <pthread.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +
> +#include <support/test-driver.h>
This can be simplified to:
#include <array_length.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <support/xthread.h>
#include <support/check.h>
#include <support/test-driver.h>
> +
> +#ifndef TST_DEADLK_TIMEOUT
> +# define TST_DEADLK_TIMEOUT 10
> +#endif
No need to handle timeout, support/test-driver.c already does that.
> +
> +#ifndef TST_DEADLK_MUTEX_PI
> +# define TST_DEADLK_MUTEX_PI 0
> +#endif
Just use the TST_DEADLK_MUTEX_PI=1 and move this test to tst-deadlk-pi.c.
> +
> +#define ARRAY_SIZE(Array) (sizeof (Array) / sizeof ((Array)[0]))
Use array_length instead.
> +
> +#define CALL_PTHREAD_OR_FAIL(Func, ...) \
> + CALL_PTHREAD_OR_EXIT (EXIT_FAILURE, Func, __VA_ARGS__)
> +
> +#define CALL_PTHREAD_OR_SKIP(Func, ...) \
> + CALL_PTHREAD_OR_EXIT (EXIT_UNSUPPORTED, Func, __VA_ARGS__)
> +
> +#define CALL_PTHREAD_OR_EXIT(Status, Func, ...) \
> + do \
> + { \
> + const int ret = Func (__VA_ARGS__); \
> + if (ret > 0) \
> + { \
> + printf ("%s:%d: %s returned positive status %d: %s", __FILE__, \
> + __LINE__, #Func, ret, strerror (ret)); \
> + exit (Status); \
> + } \
> + } \
> + while (false)
There is no need of any of these macros.
> +
> +struct howto_test
> +{
> + int type;
> + bool robust;
> + bool prio_inherit;
> +};
> +
> +struct task_context
> +{
> + pthread_mutex_t *first, *second;
> + pthread_barrier_t *barrier;
> +};
> +
> +static const struct howto_test howto[] = {
> + { .type = PTHREAD_MUTEX_ERRORCHECK,
> + .prio_inherit = TST_DEADLK_MUTEX_PI,
> + .robust = false },
> + { .type = PTHREAD_MUTEX_ERRORCHECK,
> + .prio_inherit = TST_DEADLK_MUTEX_PI,
> + .robust = true },
> + { .type = PTHREAD_MUTEX_RECURSIVE,
> + .prio_inherit = TST_DEADLK_MUTEX_PI,
> + .robust = false },
> + { .type = PTHREAD_MUTEX_RECURSIVE,
> + .prio_inherit = TST_DEADLK_MUTEX_PI,
> + .robust = true },
> +};
> +
> +static void *
> +thread_function (void *const arg)
> +{
> + const struct task_context *ctx = arg;
> + intptr_t ret = 0;
> + CALL_PTHREAD_OR_FAIL (pthread_mutex_lock, ctx->first);
> + CALL_PTHREAD_OR_FAIL (pthread_barrier_wait, ctx->barrier);
> + ret = pthread_mutex_lock (ctx->second);
> + CALL_PTHREAD_OR_FAIL (pthread_mutex_unlock, ctx->first);
> + if (ret == 0)
> + CALL_PTHREAD_OR_FAIL (pthread_mutex_unlock, ctx->second);
> + return (void *) ret;
> +}
This can be simplified to:
static void *
thread_function (void *const arg)
{
const struct task_context *ctx = arg;
intptr_t ret = 0;
xpthread_mutex_lock (ctx->first);
xpthread_barrier_wait (ctx->barrier);
ret = pthread_mutex_lock (ctx->second);
xpthread_mutex_unlock (ctx->first);
if (ret == 0)
xpthread_mutex_unlock (ctx->second);
return (void *) ret;
}
> +
> +static void
> +initialize_mutex_or_skip_test (pthread_mutex_t *const mutex,
> + const struct howto_test *const how)
> +{
> + pthread_mutexattr_t attr;
> + CALL_PTHREAD_OR_SKIP (pthread_mutexattr_init, &attr);
> + CALL_PTHREAD_OR_SKIP (pthread_mutexattr_settype, &attr, how->type);
> + if (how->robust)
> + {
> + CALL_PTHREAD_OR_SKIP (pthread_mutexattr_setrobust, &attr,
> + PTHREAD_MUTEX_ROBUST);
> + }
> + if (how->prio_inherit)
> + {
> + CALL_PTHREAD_OR_SKIP (pthread_mutexattr_setprotocol, &attr,
> + PTHREAD_PRIO_INHERIT);
> + }
> + CALL_PTHREAD_OR_SKIP (pthread_mutex_init, mutex, &attr);
> + CALL_PTHREAD_OR_SKIP (pthread_mutexattr_destroy, &attr);
> +}
And this to:
static void
initialize_mutex_or_skip_test (pthread_mutex_t *const mutex,
const struct howto_test *const how)
{
pthread_mutexattr_t attr;
xpthread_mutexattr_init (&attr);
xpthread_mutexattr_settype (&attr, how->type);
if (how->robust)
xpthread_mutexattr_setrobust (&attr, PTHREAD_MUTEX_ROBUST);
if (how->prio_inherit)
xpthread_mutexattr_setprotocol (&attr, PTHREAD_PRIO_INHERIT);
xpthread_mutex_init (mutex, &attr);
xpthread_mutexattr_destroy (&attr);
}
> +
> +static void
> +beforehand (const struct howto_test *const how)
> +{
> + printf (
> + "Testing with this mutex: type = %d, robust = %d, prio_inherit = %d\n",
> + how->type, how->robust, how->prio_inherit);
> +}
> +
> +static int
> +analyze_results (const struct howto_test *const how, const int ret1,
> + const int ret2)
> +{
> + if ((ret1 != EDEADLK) && (ret2 != EDEADLK))
> + {
> + printf ("At least one thread should have gotten %d but "
> + "threads got %d and %d respectively.\n",
> + EDEADLK, ret1, ret2);
> + return EXIT_FAILURE;
> + }
> + else if (ret1 != 0 && ret1 != EDEADLK)
> + {
> + printf ("First thread should have gotten 0 or %d but got %d "
> + "instead.\n",
> + EDEADLK, ret1);
> + return EXIT_FAILURE;
> + }
> + else if (ret2 != 0 && ret2 != EDEADLK)
> + {
> + printf ("Second thread should have gotten 0 or %d but got %d "
> + "instead.\n",
> + EDEADLK, ret2);
> + return EXIT_FAILURE;
> + }
> + else
> + {
> + printf ("Threads got %d and %d respectively which is in line with the "
> + "expectation.\n",
> + ret1, ret2);
> + return EXIT_SUCCESS;
> + }
> +}
And this to:
static void
analyze_results (const struct howto_test *const how, const int ret1,
const int ret2)
{
if ((ret1 != EDEADLK) && (ret2 != EDEADLK))
FAIL_EXIT1 ("At least one thread should have gotten %d but "
"threads got %d and %d respectively.\n",
EDEADLK, ret1, ret2);
else if (ret1 != 0 && ret1 != EDEADLK)
FAIL_EXIT1 ("First thread should have gotten 0 or %d but got %d "
"instead.\n",
EDEADLK, ret1);
else if (ret2 != 0 && ret2 != EDEADLK)
FAIL_EXIT1 ("Second thread should have gotten 0 or %d but got %d "
"instead.\n",
EDEADLK, ret2);
else
printf ("Threads got %d and %d respectively which is in line with the "
"expectation.\n",
ret1, ret2);
}
> +
> +static int
> +do_test (void)
> +{
> + for (size_t i = 0; i < ARRAY_SIZE (howto); ++i)
> + {
> + pthread_t t1, t2;
> + pthread_mutex_t m1, m2;
> + void *ret1, *ret2;
> + pthread_barrier_t barrier;
> + struct task_context ctx1
> + = { .first = &m1, .second = &m2, .barrier = &barrier };
> + struct task_context ctx2
> + = { .first = &m2, .second = &m1, .barrier = &barrier };
> + beforehand (howto + i);
> + alarm (TST_DEADLK_TIMEOUT);
> + CALL_PTHREAD_OR_FAIL (pthread_barrier_init, &barrier, NULL, 2);
> + initialize_mutex_or_skip_test (&m1, howto + i);
> + initialize_mutex_or_skip_test (&m2, howto + i);
> + CALL_PTHREAD_OR_FAIL (pthread_create, &t1, NULL, thread_function, &ctx1);
> + CALL_PTHREAD_OR_FAIL (pthread_create, &t2, NULL, thread_function, &ctx2);
> + CALL_PTHREAD_OR_FAIL (pthread_join, t1, &ret1);
> + CALL_PTHREAD_OR_FAIL (pthread_join, t2, &ret2);
> + CALL_PTHREAD_OR_FAIL (pthread_mutex_destroy, &m1);
> + CALL_PTHREAD_OR_FAIL (pthread_mutex_destroy, &m2);
> + CALL_PTHREAD_OR_FAIL (pthread_barrier_destroy, &barrier);
> + alarm (0);
> + const int verdict
> + = analyze_results (howto + i, (intptr_t) ret1, (intptr_t) ret2);
> + if (verdict != 0)
> + return verdict;
> + }
> + return 0;
> +}
> +
And this to:
static int
do_test (void)
{
for (size_t i = 0; i < array_length (howto); ++i)
{
pthread_mutex_t m1, m2;
pthread_barrier_t barrier;
struct task_context ctx1
= { .first = &m1, .second = &m2, .barrier = &barrier };
struct task_context ctx2
= { .first = &m2, .second = &m1, .barrier = &barrier };
beforehand (howto + i);
xpthread_barrier_init (&barrier, NULL, 2);
initialize_mutex_or_skip_test (&m1, howto + i);
initialize_mutex_or_skip_test (&m2, howto + i);
pthread_t t1 = xpthread_create (NULL, thread_function, &ctx1);
pthread_t t2 = xpthread_create (NULL, thread_function, &ctx2);
void *ret1 = xpthread_join (t1);
void *ret2 = xpthread_join (t2);
xpthread_mutex_destroy (&m1);
xpthread_mutex_destroy (&m2);
xpthread_barrier_destroy (&barrier);
analyze_results (howto + i, (intptr_t) ret1, (intptr_t) ret2);
}
return 0;
}
> +#include <support/test-driver.c>
@@ -283,6 +283,7 @@ tests = \
tst-cleanup5 \
tst-cond26 \
tst-context1 \
+ tst-deadlk-pi \
tst-default-attr \
tst-dlsym1 \
tst-exec4 \
@@ -418,9 +418,18 @@ __pthread_mutex_lock_full (pthread_mutex_t *mutex)
NULL, private);
if (e == ESRCH || e == EDEADLK)
{
- assert (e != EDEADLK
- || (kind != PTHREAD_MUTEX_ERRORCHECK_NP
- && kind != PTHREAD_MUTEX_RECURSIVE_NP));
+ if (e == EDEADLK
+ && (kind == PTHREAD_MUTEX_ERRORCHECK_NP
+ || kind == PTHREAD_MUTEX_RECURSIVE_NP))
+ {
+ /* FUTEX_LOCK_PI may return EDEADLK due to cross‑thread
+ * deadlock detection, beyond the same‑thread recursive
+ * check above. Pass this error through for these two
+ * mutex types; otherwise, intentionally deadlock for
+ * normal mutexes. */
+ return e;
+ }
+
/* ESRCH can happen only for non-robust PI mutexes where
the owner of the lock died. */
assert (e != ESRCH || !robust);
new file mode 100644
@@ -0,0 +1,2 @@
+#define TST_DEADLK_MUTEX_PI 1
+#include "tst-deadlk.c"
new file mode 100644
@@ -0,0 +1,183 @@
+/* This test checks behavior not required by POSIX. */
+/* https://sourceware.org/pipermail/libc-alpha/2025-December/173431.html */
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <support/test-driver.h>
+
+#ifndef TST_DEADLK_TIMEOUT
+# define TST_DEADLK_TIMEOUT 10
+#endif
+
+#ifndef TST_DEADLK_MUTEX_PI
+# define TST_DEADLK_MUTEX_PI 0
+#endif
+
+#define ARRAY_SIZE(Array) (sizeof (Array) / sizeof ((Array)[0]))
+
+#define CALL_PTHREAD_OR_FAIL(Func, ...) \
+ CALL_PTHREAD_OR_EXIT (EXIT_FAILURE, Func, __VA_ARGS__)
+
+#define CALL_PTHREAD_OR_SKIP(Func, ...) \
+ CALL_PTHREAD_OR_EXIT (EXIT_UNSUPPORTED, Func, __VA_ARGS__)
+
+#define CALL_PTHREAD_OR_EXIT(Status, Func, ...) \
+ do \
+ { \
+ const int ret = Func (__VA_ARGS__); \
+ if (ret > 0) \
+ { \
+ printf ("%s:%d: %s returned positive status %d: %s", __FILE__, \
+ __LINE__, #Func, ret, strerror (ret)); \
+ exit (Status); \
+ } \
+ } \
+ while (false)
+
+struct howto_test
+{
+ int type;
+ bool robust;
+ bool prio_inherit;
+};
+
+struct task_context
+{
+ pthread_mutex_t *first, *second;
+ pthread_barrier_t *barrier;
+};
+
+static const struct howto_test howto[] = {
+ { .type = PTHREAD_MUTEX_ERRORCHECK,
+ .prio_inherit = TST_DEADLK_MUTEX_PI,
+ .robust = false },
+ { .type = PTHREAD_MUTEX_ERRORCHECK,
+ .prio_inherit = TST_DEADLK_MUTEX_PI,
+ .robust = true },
+ { .type = PTHREAD_MUTEX_RECURSIVE,
+ .prio_inherit = TST_DEADLK_MUTEX_PI,
+ .robust = false },
+ { .type = PTHREAD_MUTEX_RECURSIVE,
+ .prio_inherit = TST_DEADLK_MUTEX_PI,
+ .robust = true },
+};
+
+static void *
+thread_function (void *const arg)
+{
+ const struct task_context *ctx = arg;
+ intptr_t ret = 0;
+ CALL_PTHREAD_OR_FAIL (pthread_mutex_lock, ctx->first);
+ CALL_PTHREAD_OR_FAIL (pthread_barrier_wait, ctx->barrier);
+ ret = pthread_mutex_lock (ctx->second);
+ CALL_PTHREAD_OR_FAIL (pthread_mutex_unlock, ctx->first);
+ if (ret == 0)
+ CALL_PTHREAD_OR_FAIL (pthread_mutex_unlock, ctx->second);
+ return (void *) ret;
+}
+
+static void
+initialize_mutex_or_skip_test (pthread_mutex_t *const mutex,
+ const struct howto_test *const how)
+{
+ pthread_mutexattr_t attr;
+ CALL_PTHREAD_OR_SKIP (pthread_mutexattr_init, &attr);
+ CALL_PTHREAD_OR_SKIP (pthread_mutexattr_settype, &attr, how->type);
+ if (how->robust)
+ {
+ CALL_PTHREAD_OR_SKIP (pthread_mutexattr_setrobust, &attr,
+ PTHREAD_MUTEX_ROBUST);
+ }
+ if (how->prio_inherit)
+ {
+ CALL_PTHREAD_OR_SKIP (pthread_mutexattr_setprotocol, &attr,
+ PTHREAD_PRIO_INHERIT);
+ }
+ CALL_PTHREAD_OR_SKIP (pthread_mutex_init, mutex, &attr);
+ CALL_PTHREAD_OR_SKIP (pthread_mutexattr_destroy, &attr);
+}
+
+static void
+beforehand (const struct howto_test *const how)
+{
+ printf (
+ "Testing with this mutex: type = %d, robust = %d, prio_inherit = %d\n",
+ how->type, how->robust, how->prio_inherit);
+}
+
+static int
+analyze_results (const struct howto_test *const how, const int ret1,
+ const int ret2)
+{
+ if ((ret1 != EDEADLK) && (ret2 != EDEADLK))
+ {
+ printf ("At least one thread should have gotten %d but "
+ "threads got %d and %d respectively.\n",
+ EDEADLK, ret1, ret2);
+ return EXIT_FAILURE;
+ }
+ else if (ret1 != 0 && ret1 != EDEADLK)
+ {
+ printf ("First thread should have gotten 0 or %d but got %d "
+ "instead.\n",
+ EDEADLK, ret1);
+ return EXIT_FAILURE;
+ }
+ else if (ret2 != 0 && ret2 != EDEADLK)
+ {
+ printf ("Second thread should have gotten 0 or %d but got %d "
+ "instead.\n",
+ EDEADLK, ret2);
+ return EXIT_FAILURE;
+ }
+ else
+ {
+ printf ("Threads got %d and %d respectively which is in line with the "
+ "expectation.\n",
+ ret1, ret2);
+ return EXIT_SUCCESS;
+ }
+}
+
+static int
+do_test (void)
+{
+ for (size_t i = 0; i < ARRAY_SIZE (howto); ++i)
+ {
+ pthread_t t1, t2;
+ pthread_mutex_t m1, m2;
+ void *ret1, *ret2;
+ pthread_barrier_t barrier;
+ struct task_context ctx1
+ = { .first = &m1, .second = &m2, .barrier = &barrier };
+ struct task_context ctx2
+ = { .first = &m2, .second = &m1, .barrier = &barrier };
+ beforehand (howto + i);
+ alarm (TST_DEADLK_TIMEOUT);
+ CALL_PTHREAD_OR_FAIL (pthread_barrier_init, &barrier, NULL, 2);
+ initialize_mutex_or_skip_test (&m1, howto + i);
+ initialize_mutex_or_skip_test (&m2, howto + i);
+ CALL_PTHREAD_OR_FAIL (pthread_create, &t1, NULL, thread_function, &ctx1);
+ CALL_PTHREAD_OR_FAIL (pthread_create, &t2, NULL, thread_function, &ctx2);
+ CALL_PTHREAD_OR_FAIL (pthread_join, t1, &ret1);
+ CALL_PTHREAD_OR_FAIL (pthread_join, t2, &ret2);
+ CALL_PTHREAD_OR_FAIL (pthread_mutex_destroy, &m1);
+ CALL_PTHREAD_OR_FAIL (pthread_mutex_destroy, &m2);
+ CALL_PTHREAD_OR_FAIL (pthread_barrier_destroy, &barrier);
+ alarm (0);
+ const int verdict
+ = analyze_results (howto + i, (intptr_t) ret1, (intptr_t) ret2);
+ if (verdict != 0)
+ return verdict;
+ }
+ return 0;
+}
+
+#include <support/test-driver.c>