22#include <sys/syscall.h>
26extern void test_robust_memory_contracts(
const char* program);
28struct shared_fixture {
30 pthread_mutex_t mutex;
37static void* identity_worker(
void* parameter) {
44static int reap_exit_code(pid_t child) {
48 result = waitpid(child, &status, 0);
49 }
while (result < 0 && errno == EINTR);
50 if (result != child || !WIFEXITED(status)) {
51 printf(
"FUTEX-CONTRACT: reap child=%d result=%d status=%#x errno=%d\n", child, result, status,
56 return WEXITSTATUS(status);
59static int reap(pid_t child) {
60 int code = reap_exit_code(child);
62 printf(
"FUTEX-CONTRACT: child=%d exit=%d\n", child, code);
73static void* exit_race_worker(
void* parameter) {
75 __atomic_add_fetch(&probe->ready, 1, __ATOMIC_RELEASE);
76 while (!__atomic_load_n(&probe->release, __ATOMIC_ACQUIRE))
81static void* raw_exit_worker(
void* parameter) {
82 exit_race_worker(parameter);
83 syscall(SYS_exit, 43);
87static int raw_exit_contracts(
void) {
92 syscall(SYS_exit, 37);
95 if (reap_exit_code(child) != 37)
97 puts(
"FUTEX-CONTRACT: raw single-thread exit status passed");
100 for (
size_t attempt = 0; attempt < 4; ++attempt) {
108 pthread_t workers[4];
109 for (
size_t i = 0; i < 2; ++i) {
110 if (pthread_create(&workers[i], NULL, exit_race_worker, &early))
113 while (__atomic_load_n(&early.ready, __ATOMIC_ACQUIRE) != 2)
115 __atomic_store_n(&early.release, 1, __ATOMIC_RELEASE);
118 for (
size_t i = 2; i < 4; ++i) {
119 if (pthread_create(&workers[i], NULL, raw_exit_worker, &late))
122 for (
size_t i = 0; i < 2; ++i) {
123 if (pthread_join(workers[i], NULL))
126 while (__atomic_load_n(&late.ready, __ATOMIC_ACQUIRE) != 2)
130 __atomic_store_n(&late.release, 1, __ATOMIC_RELEASE);
131 syscall(SYS_exit, 42);
134 const int code = reap_exit_code(child);
135 if (code != 42 && code != 43) {
136 printf(
"FUTEX-CONTRACT: concurrent raw exit status=%d attempt=%zu\n", code, attempt);
141 puts(
"FUTEX-CONTRACT: concurrent exits and clone publication passed");
146static int shared_requeue(
int first_fd,
struct shared_fixture* first,
struct shared_fixture* second,
151 pid_t child = fork();
157 struct shared_fixture* alias =
158 mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, first_fd, 0);
159 if (alias == MAP_FAILED || alias == first || munmap(first, page_size) ||
160 syscall(SYS_gettid) != getpid())
162 if (write(ready[1],
"r", 1) != 1)
166 result = syscall(SYS_futex, &alias->futex, 0, 0, NULL, NULL, 0);
167 }
while (result < 0 && errno == EINTR);
168 _exit(result == 0 ? 0 : 13);
172 int valid = read(ready[0], &token, 1) == 1 && token ==
'r';
175 clock_gettime(CLOCK_MONOTONIC, &now);
176 const time_t deadline = now.tv_sec + 3;
178 while (valid && moved == 0 && now.tv_sec < deadline) {
179 moved = syscall(SYS_futex, &first->futex, 3, 0, 1, &second->futex, 0);
182 clock_gettime(CLOCK_MONOTONIC, &now);
184 valid = valid && moved == 1 &&
185 syscall(SYS_futex, &first->futex, 1, INT_MAX, NULL, NULL, 0) == 0 &&
186 syscall(SYS_futex, &second->futex, 1, 1, NULL, NULL, 0) == 1;
187 printf(
"FUTEX-CONTRACT: shared-requeue moved=%ld valid=%d\n", moved, valid);
190 kill(child, SIGKILL);
191 return reap(child) && valid;
194static int robust_owner_exit(
int fd,
struct shared_fixture* shared,
size_t page_size) {
195 pthread_mutexattr_t attributes;
196 if (pthread_mutexattr_init(&attributes) ||
197 pthread_mutexattr_setpshared(&attributes, PTHREAD_PROCESS_SHARED) ||
198 pthread_mutexattr_setrobust(&attributes, PTHREAD_MUTEX_ROBUST) ||
199 pthread_mutex_init(&shared->mutex, &attributes))
201 pthread_mutexattr_destroy(&attributes);
204 if (pipe(ready) || pipe(release))
206 pid_t child = fork();
213 struct shared_fixture* alias = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
214 if (alias == MAP_FAILED || alias == shared || munmap(shared, page_size) ||
215 syscall(SYS_gettid) != getpid() || pthread_mutex_lock(&alias->mutex))
217 if (write(ready[1],
"r", 1) != 1)
220 if (read(release[0], &token, 1) != 1)
222 puts(
"FUTEX-CONTRACT: robust owner exiting");
225 syscall(SYS_exit, 0);
231 int valid = read(ready[0], &token, 1) == 1 && token ==
'r';
233 valid = valid && write(release[1],
"x", 1) == 1;
236 kill(child, SIGKILL);
240 puts(
"FUTEX-CONTRACT: robust recovery begin");
242 int locked = pthread_mutex_lock(&shared->mutex);
243 printf(
"FUTEX-CONTRACT: robust recovery result=%d expected=%d\n", locked, EOWNERDEAD);
245 valid = locked == EOWNERDEAD;
246 if (locked == EOWNERDEAD)
247 valid = pthread_mutex_consistent(&shared->mutex) == 0 && valid;
248 if (locked == 0 || locked == EOWNERDEAD)
249 valid = pthread_mutex_unlock(&shared->mutex) == 0 && valid;
250 puts(
"FUTEX-CONTRACT: robust owner reap begin");
252 valid = reap(child) && valid;
253 printf(
"FUTEX-CONTRACT: robust owner reap complete valid=%d\n", valid);
255 return pthread_mutex_destroy(&shared->mutex) == 0 && valid;
258static int contracts_child(
void) {
260 if (!raw_exit_contracts())
262 const long main_tid = syscall(SYS_gettid);
263 if (main_tid != getpid())
265 struct identity identities[2] = {{0}};
266 for (
size_t i = 0; i < 2; ++i) {
268 if (pthread_create(&thread, NULL, identity_worker, &identities[i]) ||
269 pthread_join(thread, NULL))
271 if (!identities[i].valid || identities[i].tid == main_tid)
274 if (identities[0].tid == identities[1].tid)
276 puts(
"FUTEX-CONTRACT: task identities passed");
278 const size_t page_size = (size_t)sysconf(_SC_PAGESIZE);
281 struct shared_fixture* mappings[2];
282 for (
size_t i = 0; i < 2; ++i) {
283 snprintf(
paths[i],
sizeof(
paths[i]),
"/tmp/futex-contract-%d-%zu", getpid(), i);
284 files[i] = open(
paths[i], O_CREAT | O_TRUNC | O_RDWR, 0600);
285 if (files[i] < 0 || ftruncate(files[i], (off_t)page_size))
287 mappings[i] = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, files[i], 0);
288 if (mappings[i] == MAP_FAILED)
290 memset(mappings[i], 0,
sizeof(*mappings[i]));
292 int valid = shared_requeue(files[0], mappings[0], mappings[1], page_size);
293 valid = robust_owner_exit(files[0], mappings[0], page_size) && valid;
294 for (
size_t i = 0; i < 2; ++i) {
295 valid = munmap(mappings[i], page_size) == 0 && valid;
296 valid = close(files[i]) == 0 && valid;
297 valid = unlink(
paths[i]) == 0 && valid;
299 return valid ? 0 : 7;
302void test_futex_contracts(
const char* program) {
303 pid_t child = fork();
305 _exit(contracts_child());
306 if (child < 0 || !reap(child)) {
307 puts(
"FUTEX-CONTRACT: FAIL task-ids-shared-requeue-robust-exit");
310 puts(
"FUTEX-CONTRACT: PASS task-ids-shared-requeue-robust-exit");
311 test_robust_memory_contracts(program);