Skip to content

wait4 and waitid never return EINTR when a handled signal arrives #461

Open

Description

@xalestar

On Linux, a blocking wait4 or waitid sleeps in do_wait (kernel/exit.c) with TASK_INTERRUPTIBLE and leaves the loop with -ERESTARTSYS once a signal is pending. Signal delivery turns that into EINTR when the handler lacks SA_RESTART, and restarts the wait when it has it. Under elfuse the wait ignores the signal and returns only when the child exits: the polling loops in sys_wait4 and sys_waitid (src/syscall/proc.c) check thread_stop_requested() but never signal_claim_interruption(), which the poll, futex and pipe paths use for the same purpose.

Reproducer (the child signals the parent at 0.2 s and exits 1 s later; the handler has no SA_RESTART):

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

static void on_usr1(int sig)
{
    (void) sig;
}

static double now(void)
{
    struct timespec t;
    clock_gettime(CLOCK_MONOTONIC, &t);
    return t.tv_sec + t.tv_nsec / 1e9;
}

static void run(const char *name, int mode)
{
    pid_t child = fork();
    if (child == 0) {
        usleep(200000);
        kill(getppid(), SIGUSR1);
        sleep(1);
        _exit(0);
    }
    double t0 = now();
    int status;
    siginfo_t si;
    long ret;
    if (mode == 0)
        ret = waitpid(child, &status, 0);
    else if (mode == 1)
        ret = waitpid(-1, &status, 0);
    else
        ret = waitid(P_PID, child, &si, WEXITED);
    int err = errno;
    printf("%-10s ret=%ld %s after %.2fs\n", name, ret,
           ret < 0 ? strerror(err) : "", now() - t0);
    if (ret < 0)
        waitpid(child, &status, 0);
}

int main(void)
{
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = on_usr1;
    sigaction(SIGUSR1, &sa, NULL);
    run("wait4(pid)", 0);
    run("wait4(-1)", 1);
    run("waitid", 2);
    return 0;
}
wait4(pid) wait4(-1) waitid
elfuse c2785c7 child pid after 1.2 s child pid after 1.2 s 0 after 1.2 s
Linux 6.12 under qemu -1, EINTR after 0.2 s -1, EINTR after 0.2 s -1, EINTR after 0.2 s

The SA_RESTART half needs its own handling: elfuse has no general ERESTARTSYS path, so a loop that starts honoring the claimed signal has to keep waiting when that signal's handler has SA_RESTART. A regression test needs the child to stay alive until the handler releases it, and should cover EINTR without SA_RESTART and a successful reap with it, for wait4(pid), wait4(-1) and waitid.

Found by jserv in #460 (comment): the race fixed there never showed under elfuse for this reason.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions