Repository navigation
test_sigwaitinfo fails on NetBSD due to POSIX violation #137490
Copy link
Copy link
Closed
Labels
3.13only security fixesonly security fixes3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixesOS-netbsdtestsTests in the Lib/test dirTests in the Lib/test dirtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Description
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-pydebugTest
╰─$ ./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
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.13only security fixesonly security fixes3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixesOS-netbsdtestsTests in the Lib/test dirTests in the Lib/test dirtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Activity
Merge branch 'main' into pythongh-137490
serhiy-storchaka commentedon Aug 7, 2025
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()andsigtimedwait()on NetBSD?pythongh-137490: Fix signal.sigwaitinfo() on NetBSD
serhiy-storchaka commentedon Aug 7, 2025
#137523 is an alternative solution. I am not aware of any drawbacks of it.
furkanonder commentedon Aug 7, 2025
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()returnserrno = 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():
According to NetBSD's sigwaitinfo():
Issues:
furkanonder commentedon Aug 7, 2025
Since I believe this is a NetBSD kernel and documentation error, I decided to skip the test for NetBSD instead of handling this situation.
serhiy-storchaka commentedon Aug 8, 2025
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 usesigtimedwait()with very large timeout or repeatsigtimedwait()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.
serhiy-storchaka commentedon Aug 10, 2025
@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?furkanonder commentedon Aug 10, 2025
The issue has been reported to NetBSD.
serhiy-storchaka commentedon Aug 11, 2025
Thank you.
furkanonder commentedon Aug 11, 2025
You are welcome.
serhiy-storchaka commentedon Sep 15, 2025
There is no reaction from the NetBSD developers, so I accept that solution.
gh-137490: Fix signal.sigwaitinfo() on NetBSD (GH-137523)
pythongh-137490: Fix signal.sigwaitinfo() on NetBSD (pythonGH-137523)
pythongh-137490: Fix signal.sigwaitinfo() on NetBSD (pythonGH-137523)
[3.13] gh-137490: Fix signal.sigwaitinfo() on NetBSD (GH-137523) (GH-…
[3.14] gh-137490: Fix signal.sigwaitinfo() on NetBSD (GH-137523) (GH-…
riastradh commentedon Dec 19, 2025
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!
serhiy-storchaka commentedon Dec 19, 2025
Thank you for letting us know. Now the workaround in Python is no longer needed, although it will take time before it is removed.