Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

`wait4` and `waitid` never return `EINTR` when a handled signal arrives

Open
#461 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 3 days

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
54/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
c

Research direction

Start in src/syscall/proc.c at the polling loops in sys_wait4 and sys_waitid, then compare how the poll, futex and pipe paths use signal_claim_interruption(). The issue describes the required behavior for handlers with and without SA_RESTART and provides a reproducer. Add regression coverage for wait4(pid), wait4(-1) and waitid, checking EINTR without SA_RESTART and successful reap with it.

Written by the indexing model from the issue text.

Description

bug

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 https://github.com/sysprog21/elfuse/pull/460#discussion_r4348510702: the race fixed there never showed under elfuse for this reason.

Dominant language
C
Stars
275
Forks
29
Avg merge
2d 22h
Merged PRs (30d)
18

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from sysprog21/elfuse

All issues in sysprog21/elfuse

Similar issues

More C issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.