glib: reset errno to 0 when futex() returns EAGAIN

This commit is contained in:
Daniel P. Berrange 2023-06-22 14:22:03 +00:00 committed by Philip Withnall
parent 3637c2c47f
commit 1bfc04a13d
2 changed files with 89 additions and 11 deletions

View File

@ -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"

View File

@ -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;