diff --git a/runtime/platform/linux.c b/runtime/platform/linux.c index 4851de73..66b7cb2d 100644 --- a/runtime/platform/linux.c +++ b/runtime/platform/linux.c @@ -2373,6 +2373,22 @@ static void spawn_close_from(posix_spawn_file_actions_t *fa, int from) { if (posix_spawn_file_actions_addclosefrom_np) posix_spawn_file_actions_addclosefrom_np(fa, from); } +// The calling thread's mask with every processor this process uses, for the time of a posix_spawn: a child starts +// with the mask of the thread that spawned it, and after plat_cpu_reserve no thread has them all (the recording thread +// has its core alone, every other thread lacks that core). The game plat_restart starts would begin without the core, +// take the next fast one for its own recording thread, and so have one core fewer after every Restart. +// 1 when the mask was widened: *saved is then the mask to put back. +static int spawn_mask_whole(cpu_set_t *saved) { + cpu_set_t whole; + plat_mutex_lock(&cpu_lock); + int taken = cpu_taker != 0; + CPU_OR(&whole, &cpu_taken, &cpu_others); + plat_mutex_unlock(&cpu_lock); + if (!taken || sched_getaffinity(0, sizeof *saved, saved)) return 0; + CPU_OR(&whole, &whole, saved); + return !CPU_EQUAL(&whole, saved) && !sched_setaffinity(0, sizeof whole, &whole); +} + int plat_restart(void) { // the arguments, NUL-separated, from /proc/self/cmdline static char args[16384]; @@ -2399,8 +2415,11 @@ int plat_restart(void) { posix_spawnattr_setsigdefault(&at, &all); posix_spawnattr_setflags(&at, POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF); setenv(RESTART_WAIT, wait, 1); + cpu_set_t mask; + int whole = spawn_mask_whole(&mask); pid_t pid; int e = posix_spawn(&pid, exe, &fa, &at, argv, environ); + if (whole) sched_setaffinity(0, sizeof mask, &mask); unsetenv(RESTART_WAIT); posix_spawnattr_destroy(&at); posix_spawn_file_actions_destroy(&fa); diff --git a/tests/platform_restart_mask_test.c b/tests/platform_restart_mask_test.c new file mode 100644 index 00000000..d6678689 --- /dev/null +++ b/tests/platform_restart_mask_test.c @@ -0,0 +1,82 @@ +// plat_restart on Linux (runtime/platform/linux.c): the game it starts may run on every processor this one could +// before plat_cpu_reserve took a core for the recording thread, whichever thread asks for the restart. No game files. +// Build and run from the repository root: +// clang -O2 -ffunction-sections -fdata-sections -Iruntime -c runtime/platform/linux.c -o /tmp/plat.o +// clang -O2 -Wall -Iruntime tests/platform_restart_mask_test.c /tmp/plat.o -Wl,--gc-sections -lpthread -ldl -o /tmp/restart_mask +// /tmp/restart_mask (exit 77: this machine has no core plat_cpu_reserve would take) +// The test starts itself again through plat_restart; that copy only writes how many processors it may run on. +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include "platform/platform.h" + +#define OUT "RESTART_MASK_TEST_OUT" + +static int allowed(void) { + cpu_set_t set; + return sched_getaffinity(0, sizeof set, &set) ? -1 : CPU_COUNT(&set); +} + +// What the copy plat_restart started wrote, waiting up to 5 s for it; -1 when it never came. +static int restarted_count(const char *path) { + for (int i = 0; i < 500; i++) { + FILE *f = fopen(path, "r"); + int n = -1; + if (f) { + int got = fscanf(f, "%d", &n); + fclose(f); + if (got == 1) { + remove(path); + return n; + } + } + usleep(10000); + } + return -1; +} + +static atomic_int go, done; + +// A thread that existed before the core was taken, as the game's main thread does: it asks for the restart. +static void *other_thread(void *arg) { + (void)arg; + while (!atomic_load(&go)) usleep(1000); + plat_restart(); + atomic_store(&done, 1); + return NULL; +} + +int main(void) { + const char *out = getenv(OUT); + if (getenv("NX2NAVI_RESTART_WAIT") && out) { // the copy plat_restart started + FILE *f = fopen(out, "w"); + if (f) fprintf(f, "%d\n", allowed()), fclose(f); + return 0; + } + char path[64]; + snprintf(path, sizeof path, "/tmp/restart-mask-%d.txt", (int)getpid()); + setenv(OUT, path, 1); + remove(path); + int before = allowed(), fails = 0; + pthread_t th; + pthread_create(&th, NULL, other_thread, NULL); + char which[128]; + int core = plat_cpu_reserve(which, sizeof which); + if (!core) { puts("SKIP: no core to reserve on this machine"); return 77; } + atomic_store(&go, 1); + while (!atomic_load(&done)) usleep(1000); + pthread_join(th, NULL); + int from_other = restarted_count(path); + plat_restart(); // and from the thread that holds the core + int from_holder = restarted_count(path); + printf("%d processors before; the core taken: %s; a restart from another thread starts with %d, from the holder with %d\n", + before, which, from_other, from_holder); + if (from_other != before) puts("FAIL: the restarted process lacks processors (asked by another thread)"), fails++; + if (from_holder != before) puts("FAIL: the restarted process lacks processors (asked by the holder)"), fails++; + if (!fails) puts("restart mask: the restarted process has every processor"); + return fails != 0; +}