diff --git a/glib/gthreadprivate.h b/glib/gthreadprivate.h index 74d37ba32..9de3d06ce 100644 --- a/glib/gthreadprivate.h +++ b/glib/gthreadprivate.h @@ -54,6 +54,12 @@ struct _GRealThread /* Wrapper macro to call `futex_time64` and/or `futex` with simple * parameters and without returning the return value. * + * We expect futex to sometimes return EAGAIN due to the race + * between the caller checking the current value and deciding to + * do the futex op. To avoid splattering errno on success, we + * restore the original errno if EAGAIN is seen. See also: + * https://gitlab.gnome.org/GNOME/glib/-/issues/3034 + * * If the `futex_time64` syscall does not exist (`ENOSYS`), we retry again * with the normal `futex` syscall. This can happen if newer kernel headers * are used than the kernel that is actually running. @@ -70,23 +76,37 @@ struct _GRealThread if (res < 0 && errno == ENOSYS) \ { \ errno = saved_errno; \ - syscall (__NR_futex, uaddr, (gsize) futex_op, __VA_ARGS__); \ + res = syscall (__NR_futex, uaddr, (gsize) futex_op, __VA_ARGS__); \ + } \ + if (res < 0 && errno == EAGAIN) \ + { \ + errno = saved_errno; \ } \ } \ G_STMT_END #elif defined(__NR_futex_time64) -#define g_futex_simple(uaddr, futex_op, ...) \ - G_STMT_START \ - { \ - syscall (__NR_futex_time64, uaddr, (gsize) futex_op, __VA_ARGS__); \ - } \ +#define g_futex_simple(uaddr, futex_op, ...) \ + G_STMT_START \ + { \ + int saved_errno = errno; \ + int res = syscall (__NR_futex_time64, uaddr, (gsize) futex_op, __VA_ARGS__); \ + if (res < 0 && errno == EAGAIN) \ + { \ + errno = saved_errno; \ + } \ + } \ G_STMT_END #elif defined(__NR_futex) -#define g_futex_simple(uaddr, futex_op, ...) \ - G_STMT_START \ - { \ - syscall (__NR_futex, uaddr, (gsize) futex_op, __VA_ARGS__); \ - } \ +#define g_futex_simple(uaddr, futex_op, ...) \ + G_STMT_START \ + { \ + int saved_errno = errno; \ + int res = syscall (__NR_futex, uaddr, (gsize) futex_op, __VA_ARGS__); \ + if (res < 0 && errno == EAGAIN) \ + { \ + errno = saved_errno; \ + } \ + } \ G_STMT_END #else /* !defined(__NR_futex) && !defined(__NR_futex_time64) */ #error "Neither __NR_futex nor __NR_futex_time64 are defined but were found by meson" diff --git a/glib/tests/mutex.c b/glib/tests/mutex.c index 2d0ef1ff3..bd484c59e 100644 --- a/glib/tests/mutex.c +++ b/glib/tests/mutex.c @@ -159,6 +159,63 @@ test_mutex5 (void) g_assert (owners[i] == NULL); } +static gpointer +test_mutex_errno_func (gpointer data) +{ + GMutex *m = data; + + g_test_summary ("Validates that errno is not touched upon return"); + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3034"); + + for (unsigned int i = 0; i < 1000; i++) + { + errno = 0; + g_mutex_lock (m); + g_assert_cmpint (errno, ==, 0); + + g_thread_yield (); + + errno = 0; + g_mutex_unlock (m); + g_assert_cmpint (errno, ==, 0); + + errno = 0; + if (g_mutex_trylock (m)) + { + g_assert_cmpint (errno, ==, 0); + + g_thread_yield (); + + errno = 0; + g_mutex_unlock (m); + g_assert_cmpint (errno, ==, 0); + } + } + + return NULL; +} + +static void +test_mutex_errno (void) +{ + gsize i; + GThread *threads[THREADS]; + GMutex m; + + g_mutex_init (&m); + + for (i = 0; i < G_N_ELEMENTS (threads); i++) + { + threads[i] = g_thread_new ("test_mutex_errno", + test_mutex_errno_func, &m); + } + + for (i = 0; i < G_N_ELEMENTS (threads); i++) + { + g_thread_join (threads[i]); + } +} + static gint count_to = 0; static gboolean @@ -226,6 +283,7 @@ main (int argc, char *argv[]) g_test_add_func ("/thread/mutex3", test_mutex3); g_test_add_func ("/thread/mutex4", test_mutex4); g_test_add_func ("/thread/mutex5", test_mutex5); + g_test_add_func ("/thread/mutex/errno", test_mutex_errno); { guint i;