Skip to content

test_sigwaitinfo fails on NetBSD due to POSIX violation #137490

Closed

Description

@furkanonder

Bug report

Bug description:

The SignalEINTRTest.test_sigwaitinfo test in test_eintr fails on NetBSD. According to the official POSIX.1-2024 specification for sigwaitinfo:

"The sigtimedwait() and sigwaitinfo() functions may fail if: [EINTR] The wait was interrupted by an unblocked, caught signal."

NetBSD's sigwaitinfo() implementation violates POSIX by returning errno = ECANCELED (87) instead of errno = EINTR (4) when interrupted by a signal not in the wait set.

Configuration

./configure --with-pydebug

Test

╰─$ ./python -m unittest test._test_eintr.SignalEINTRTest.test_sigwaitinfo -v
test_sigwaitinfo (test._test_eintr.SignalEINTRTest.test_sigwaitinfo) ... ERROR

======================================================================
ERROR: test_sigwaitinfo (test._test_eintr.SignalEINTRTest.test_sigwaitinfo)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/blue/Desktop/cpython/Lib/test/_test_eintr.py", line 440, in test_sigwaitinfo
    self.check_sigwait(wait_func)
    ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
  File "/home/blue/Desktop/cpython/Lib/test/_test_eintr.py", line 430, in check_sigwait
    wait_func(signum)
    ~~~~~~~~~^^^^^^^^
  File "/home/blue/Desktop/cpython/Lib/test/_test_eintr.py", line 438, in wait_func
    signal.sigwaitinfo([signum])
    ~~~~~~~~~~~~~~~~~~^^^^^^^^^^
OSError: [Errno 87] Operation canceled

----------------------------------------------------------------------
Ran 1 test in 0.112s

FAILED (errors=1)

Reproduction

I created a minimal C program that reproduces the same issue.

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

volatile sig_atomic_t got_sigalrm = 0;

void sigalrm_handler(int sig) {
    got_sigalrm = 1;
    printf("SIGALRM handler called\n");
}

int main() {
    printf("sigwaitinfo interruption test\n");
    
    sigset_t sigset;
    siginfo_t info;
    int target_signal = SIGUSR1;
    
    // Set SIGALRM handler
    struct sigaction sa = {0};
    sa.sa_handler = sigalrm_handler;
    sigaction(SIGALRM, &sa, NULL);
    
    // Block SIGUSR1 and save old mask
    sigemptyset(&sigset);
    sigaddset(&sigset, target_signal);
    pthread_sigmask(SIG_BLOCK, &sigset, NULL);
    printf("Blocked SIGUSR1, waiting with sigwaitinfo...\n");
    
    pid_t pid = fork();
    if (pid == 0) {
        sleep(1);
        printf("Child: sending SIGALRM to interrupt parent\n");
        kill(getppid(), SIGALRM);
        exit(0);
    }
    
    printf("Parent: calling sigwaitinfo([SIGUSR1])...\n");
    int result = sigwaitinfo(&sigset, &info);
    
    if (result == -1) {
        printf("sigwaitinfo failed: errno = %d (%s)\n", errno, strerror(errno));
    }
    
    printf("SIGALRM handler was called: %s\n", got_sigalrm ? "yes" : "no");
    
    int status;
    waitpid(pid, &status, 0);
    printf("Child exited with status: %d\n", WEXITSTATUS(status));
    
    return 0;
}

Output on NetBSD 10.0:

sigwaitinfo interruption test
Blocked SIGUSR1, waiting with sigwaitinfo...
Parent: calling sigwaitinfo([SIGUSR1])...
Child: sending SIGALRM to interrupt parent
SIGALRM handler called
sigwaitinfo failed: errno = 87 (Operation canceled)
SIGALRM handler was called: yes
Child exited with status: 0

Expected POSIX-compliant output (Linux):

sigwaitinfo interruption test
Blocked SIGUSR1, waiting with sigwaitinfo...
Parent: calling sigwaitinfo([SIGUSR1])...
Child: sending SIGALRM to interrupt parent
SIGALRM handler called
sigwaitinfo failed: errno = 4 (Interrupted system call)
SIGALRM handler was called: yes
Child exited with status: 0

CPython versions tested on:

CPython main branch, 3.15, 3.14, 3.13

Operating systems tested on:

Other

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    testsTests in the Lib/test dir
    3.13only security fixes
    3.14bugs and security fixes
    3.15bugs and security fixes
    on Aug 6, 2025
  2. serhiy-storchaka commented on Aug 7, 2025

    @serhiy-storchaka
    Member

    Is returning ECANCELED on NetBSD documented anywhere?

    It seems that sigtimedwait() only fails if timeout is not NULL.

    Should not we handle ECANCELED the same way as EINTR for sigwaitinfo() and sigtimedwait() on NetBSD?

  3. added a commit that references this issue on Aug 7, 2025
  4. serhiy-storchaka commented on Aug 7, 2025

    @serhiy-storchaka
    Member

    #137523 is an alternative solution. I am not aware of any drawbacks of it.

  5. furkanonder commented on Aug 7, 2025

    @furkanonder
    ContributorAuthor

    Is returning ECANCELED on NetBSD documented anywhere?

    According to Google: site:man.netbsd.org ECANCELED, ECANCELED is mentioned for: aio_cancel, strtou, errno, aio_write, timerfd_create, strtoi.

    However, NetBSD's sigwaitinfo() returns errno = ECANCELED (87) when interrupted by signals, but this behavior is completely undocumented and contradicts NetBSD's own manual page.

    Comparison with FreeBSD

    According to FreeBSD's sigwaitinfo():

    The sigtimedwait() and sigwaitinfo() system calls fail if:
    [EINTR] The wait was interrupted by an unblocked, caught signal.
    

    According to NetBSD's sigwaitinfo():

    sigwaitinfo() and sigwait() always succeed.
    sigtimedwait() may also fail if:
    [EAGAIN] No signal specified in set was generated in the specified timeout.
    [EINVAL] The specified timeout was invalid.
    

    Issues:

    • NetBSD doesn't document that sigwaitinfo can be interrupted
    • False claim: Says sigwaitinfo "always succeed" (factually incorrect)
    • Missing EINTR documentation that exists in FreeBSD/POSIX
  6. furkanonder commented on Aug 7, 2025

    @furkanonder
    ContributorAuthor

    Should not we handle ECANCELED the same way as EINTR for sigwaitinfo() and sigtimedwait() on NetBSD?

    Since I believe this is a NetBSD kernel and documentation error, I decided to skip the test for NetBSD instead of handling this situation.

  7. serhiy-storchaka commented on Aug 8, 2025

    @serhiy-storchaka
    Member

    It's not about the tests. This may be a bug in NetBSD's implementation, but what the users of programs implemented in Python should do? It is possible to write a workaround -- an explicit loop around signal.sigwaitinfo() if it raises OSError with errno=ECANCELED. But since this is NetBSD specific behavior, it will likely not be handled, unless the author tested their code on NetBSD. This leaves users with programs not working on NetBSD.

    If there is other legitimate way to make sigwaitinfo() failing with errno=ECANCELED, then ECANCELED should not be ignored. Other possible workarounds are to use sigtimedwait() with very large timeout or repeat sigtimedwait() if it fails with errno=EAGAIN.

    For now, I think that handling ECANCELED the same way as EINTR is safe. There is no setting ECANCELED in the NetBSD's libc, it is set in the kernel, and there is a code that turns EINTR into ECANCELED. I wonder what other sites that handle EINTR need to handle ECANCELED on NetBSD.

  8. serhiy-storchaka commented on Aug 10, 2025

    @serhiy-storchaka
    Member

    @furkanonder, could you please report this issue to NetBSD? Even if they not fix it, but just document as a NetBSD peculiarity, maybe they can say if repeating the sigwaitinfo() after getting ECANCELED is safe, and what other functions can set ECANCELED instead of EINTR?

  9. furkanonder commented on Aug 10, 2025

    @furkanonder
    ContributorAuthor

    @furkanonder, could you please report this issue to NetBSD? Even if they not fix it, but just document as a NetBSD peculiarity, maybe they can say if repeating the sigwaitinfo() after getting ECANCELED is safe, and what other functions can set ECANCELED instead of EINTR?

    The issue has been reported to NetBSD.

  10. serhiy-storchaka commented on Aug 11, 2025

    @serhiy-storchaka
    Member

    Thank you.

  11. furkanonder commented on Aug 11, 2025

    @furkanonder
    ContributorAuthor

    Thank you.

    You are welcome.

  12. serhiy-storchaka commented on Sep 15, 2025

    @serhiy-storchaka
    Member

    There is no reaction from the NetBSD developers, so I accept that solution.

  13. added a commit that references this issue on Sep 15, 2025
  14. added 2 commits that reference this issue on Sep 15, 2025
  15. added a commit that references this issue on Sep 15, 2025
  16. added a commit that references this issue on Oct 8, 2025
  17. riastradh commented on Dec 19, 2025

    @riastradh

    @furkanonder, could you please report this issue to NetBSD? Even if they not fix it, but just document as a NetBSD peculiarity, maybe they can say if repeating the sigwaitinfo() after getting ECANCELED is safe, and what other functions can set ECANCELED instead of EINTR?

    It's just sigtimedwait, sigwaitinfo, and sigwait that have this bug, from a mistake in the kernel logic to implement sigtimedwait.

    It was never intended for them to leak ECANCELED to callers; that was meant purely for internal use of a long-abandoned multithreading architecture (scheduler activations, or SA), but a mistake in ripping out SA two decades ago accidentally led to leaking ECANCELED where it should have returned EINTR. Nothing special is meant by ECANCELED here: treating it as EINTR on the affected NetBSD versions is the right thing. So repeating on ECANCELED, just like you repeat on EINTR, is appropriate.

    I posted all the historical details on the NetBSD bug report at https://gnats.NetBSD.org/59586, and committed a fix which should go out in the next releases. Sorry for the long delay in followup!

  18. serhiy-storchaka commented on Dec 19, 2025

    @serhiy-storchaka
    Member

    Thank you for letting us know. Now the workaround in Python is no longer needed, although it will take time before it is removed.

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

    3.13only security fixes3.14bugs and security fixes3.15bugs and security fixesOS-netbsdtestsTests in the Lib/test dirtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions