On Thu, Jun 26 2025 at 14:11, André Almeida wrote:
+int set_robust_list(struct robust_list_head *head, size_t len)
This function and the get() counterpart are global because they can?
+{
- return syscall(SYS_set_robust_list, head, len);
 +} +/*
- Basic lock struct, contains just the futex word and the robust list element
 
- Real implementations have also a *prev to easily walk in the list
 - */
 +struct lock_struct {
- _Atomic(unsigned int) futex;
 - struct robust_list list;
 
tabular arrangement please.
- pthread_barrier_wait(&barrier);
 - /*
 * There's a race here: the parent thread needs to be inside* futex_wait() before the child thread dies, otherwise it will miss the* wakeup from handle_futex_death() that this child will emit. We wait a* little bit just to make sure that this happens.*/- sleep(1);
 
One second is quite a little bit. :)
- /*
 * futex_wait() should return 0 and the futex word should be marked with* FUTEX_OWNER_DIED*/- ASSERT_EQ(ret, 0);
 - if (ret != 0)
 printf("futex wait returned %d", errno);
What's the purpose of the extra printf() after the assert here? This code is not even reached when ret != 0, no?
- ASSERT_TRUE(*futex | FUTEX_OWNER_DIED);
 
That's always true no matter what the content of the futex variable is, no?
+/*
- The only valid value for len is sizeof(*head)
 - */
 +static void test_set_robust_list_invalid_size(void) +{
- struct robust_list_head head;
 - size_t head_size = sizeof(struct robust_list_head);
 
Groan. You already define the robust_list_head variable ahead of head_size and violate the reverse fir tree ordering, so why don't you use the obvious and actually robust 'sizeof(head)'?
+/*
- Test get_robust_list with pid = 0, getting the list of the running thread
 - */
 +static void test_get_robust_list_self(void) +{
- struct robust_list_head head, head2, *get_head;
 - size_t head_size = sizeof(struct robust_list_head), len_ptr;
 
Ditto.
+static int child_list(void *arg) +{
- struct robust_list_head *head = (struct robust_list_head *) arg;
 
void pointers really don't require type casts
- int ret;
 - ret = set_robust_list(head, sizeof(struct robust_list_head));
 
sizeof(*head)
- if (ret)
 ksft_test_result_fail("set_robust_list error\n");- pthread_barrier_wait(&barrier);
 - pthread_barrier_wait(&barrier2);
 
Lacks a comment what this waits for
- return 0;
 +}
+/*
- Test get_robust_list from another thread. We use two barriers here to ensure
 
- that:
 
- the child thread set the list before we try to get it from the
 
- parent
 
- the child thread still alive when we try to get the list from it
 - */
 +static void test_get_robust_list_child(void) +{
- pid_t tid;
 - int ret;
 - struct robust_list_head head, *get_head;
 - size_t len_ptr;
 
Reverse fir tree ordering please.
- ret = pthread_barrier_init(&barrier, NULL, 2);
 - ret = pthread_barrier_init(&barrier2, NULL, 2);
 - ASSERT_EQ(ret, 0);
 - tid = create_child(&child_list, &head);
 - ASSERT_NE(tid, -1);
 - pthread_barrier_wait(&barrier);
 - ret = get_robust_list(tid, &get_head, &len_ptr);
 - ASSERT_EQ(ret, 0);
 - ASSERT_EQ(&head, get_head);
 - pthread_barrier_wait(&barrier2);
 - wait(NULL);
 - pthread_barrier_destroy(&barrier);
 - pthread_barrier_destroy(&barrier2);
 - ksft_test_result_pass("%s\n", __func__);
 +}
+static int child_fn_lock_with_error(void *arg) +{
- struct lock_struct *lock = (struct lock_struct *) arg;
 
See above
- struct robust_list_head head;
 - int ret;
 - ret = set_list(&head);
 - if (ret)
 ksft_test_result_fail("set_robust_list error\n");
So you fail the test and continue to produce more fails or what? Why does this not use one of these ASSERT thingies or return?
- ret = mutex_lock(lock, &head, true);
 - if (ret)
 ksft_test_result_fail("mutex_lock error\n");- pthread_barrier_wait(&barrier);
 - sleep(1);
 - return 0;
 +}
+/*
- Same as robustness test, but inject an error where the mutex_lock() exits
 
- earlier, just after setting list_op_pending and taking the lock, to test the
 
- list_op_pending mechanism
 - */
 +static void test_set_list_op_pending(void) +{
- struct lock_struct lock = { .futex = 0 };
 - struct robust_list_head head;
 - _Atomic(unsigned int) *futex = &lock.futex;
 - int ret;
 
See above
- ASSERT_EQ(ret, 0);
 - if (ret != 0)
 printf("futex wait returned %d", errno);
The random insertion of completely pointless printf()'s is stunning.
- ASSERT_TRUE(*futex | FUTEX_OWNER_DIED);
 
Yet another always true assert which is happily optimized out by the compiler.
- wait(NULL);
 - pthread_barrier_destroy(&barrier);
 - ksft_test_result_pass("%s\n", __func__);
 +}
+static int child_wait_lock(void *arg) +{
- struct lock_struct *lock = (struct lock_struct *) arg;
 - struct robust_list_head head;
 - int ret;
 - pthread_barrier_wait(&barrier2);
 - ret = mutex_lock(lock, &head, false);
 - if (ret)
 ksft_test_result_fail("mutex_lock error\n");- if (!(lock->futex | FUTEX_OWNER_DIED))
 ksft_test_result_fail("futex not marked with FUTEX_OWNER_DIED\n");
Now I kinda understand this insanity. The child emits a fail and exits. Then the parent ...
- for (i = 0; i < CHILD_NR; i++)
 create_child(&child_wait_lock, &locks[i]);- /* Wait for all children to return */
 - while (wait(NULL) > 0);
 - pthread_barrier_destroy(&barrier);
 - pthread_barrier_destroy(&barrier2);
 - ksft_test_result_pass("%s\n", __func__);
 
... happily claims that the test passed.
Seriously?
Thread functions have a return value for a reason and wait(2) has a wstatus argument for the very same reason.
+static int child_circular_list(void *arg) +{
- static struct robust_list_head head;
 - struct lock_struct a, b, c;
 - int ret;
 - ret = set_list(&head);
 - if (ret)
 ksft_test_result_fail("set_list error\n");
Yet another instance of the same ....
Thanks,
tglx