signal(7): why does it say that pthread_mutex_lock() and thread_cond_wait() can fail with EINTR?
In man/man7/signal.7 it says:
Quoted 6 lines
If a blocked call to one of the following interfaces is interrupted by a signal handler, then the call is automatically restarted after the signal handler returns if the
SA_RESTARTflag was used; otherwise the call fails with the error EINTR:
(...)
•pthread_mutex_lock(3),pthread_cond_wait(3), and related APIs.
I don't understand this, in my experiments neither pthread_mutex_lock() nor pthread_cond_wait() return EINTR even if signal handler was installed without using SA_RESTART flag. The underlying futex() call indeed fails with EINTR but it's called again by both glibc and musl. Additionally both man/man3/pthread_mutex_lock.3 and man/man3/pthread_cond_wait.3 say that these functions do not return EINTR.
Is my understanding of the signal.7 wrong or does it need some work?
Signature · 1 line
Arkadiusz Drabczyk <[email protected]>
Alejandro Colomar
maintainer[CC += libc-help]
Hi Arkadiusz,
Quoted 9 lines · Arkadiusz Drabczyk
In man/man7/signal.7 it says:
Quoted 6 lines
If a blocked call to one of the following interfaces is interrupted by a signal handler, then the call is automatically restarted after the signal handler returns if the
SA_RESTARTflag was used; otherwise the call fails with the error EINTR:
(...)
•pthread_mutex_lock(3),pthread_cond_wait(3), and related APIs.
Quoted 3 lines
I don't understand this, in my experiments neither
pthread_mutex_lock()norpthread_cond_wait()return EINTR even if signal handler was installed without usingSA_RESTARTflag.
Please show some minimal examples if you can.
Quoted 3 lines
The
underlyingfutex()call indeed fails with EINTR but it's called again by both glibc and musl.
I've CCed glibc, in case they can comment. Maybe this behavior changed at some point in the past? I don't know.
Quoted 3 lines
Additionally both
man/man3/pthread_mutex_lock.3 and man/man3/pthread_cond_wait.3 say that these functions do not return EINTR.
Those pages are too old. They were unmaintained in a Debian package, and we adopted them recently. I wouldn't trust them blindly.
Quoted 1 line
Is my understanding of the signal.7 wrong or does it need some work?
It might need some work. Thanks for the report!
Have a lovely new year!
Alex
Signature · 1 line
<https://www.alejandro-colomar.es/>
* Alejandro Colomar:
Quoted 5 lines
Quoted 2 lines
The underlying
futex()call indeed fails with EINTR but it's called again by both glibc and musl.I've CCed glibc, in case they can comment. Maybe this behavior changed at some point in the past? I don't know.
Maybe in the LinuxThreads implementation. I think NPTL has handled this correctly from the beginning. POSIX bans the EINTR failure condition.
Thanks,
Florian
Alejandro Colomar
maintainerHi Florian, Arkadiusz,
Quoted 10 lines · Florian Weimer
* Alejandro Colomar:
Quoted 5 lines
Quoted 2 lines
The underlying
futex()call indeed fails with EINTR but it's called again by both glibc and musl.I've CCed glibc, in case they can comment. Maybe this behavior changed at some point in the past? I don't know.
Maybe in the LinuxThreads implementation. I think NPTL has handled this correctly from the beginning. POSIX bans the EINTR failure condition.
Thanks!
Arkadiusz, would you do the honours writing a patch? Should I?
Have a lovely new year!
Alex
Quoted 4 lines
Thanks,
Florian
Signature · 1 line
<https://www.alejandro-colomar.es/>
Quoted 14 lines · Alejandro Colomar
[CC += libc-help]
Hi Arkadiusz,
Quoted 9 lines · Arkadiusz Drabczyk
In man/man7/signal.7 it says:
Quoted 6 lines
If a blocked call to one of the following interfaces is interrupted by a signal handler, then the call is automatically restarted after the signal handler returns if the
SA_RESTARTflag was used; otherwise the call fails with the error EINTR:
(...)
•pthread_mutex_lock(3),pthread_cond_wait(3), and related APIs.
Quoted 5 lines
Quoted 3 lines
I don't understand this, in my experiments neither
pthread_mutex_lock()norpthread_cond_wait()return EINTR even if signal handler was installed without usingSA_RESTARTflag.Please show some minimal examples if you can.
Here is the minimal example for pthread_mutex_lock() with all checks:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
pthread_mutex_t m;
void handle_sigint(int sig)
{
(void)sig;
write(STDOUT_FILENO, "SIGINT", 6);
}void *example_thread(void *arg)
{
(void)arg;
int ret = pthread_mutex_lock(&m);
if (ret) {
fprintf(stderr, "pthread_mutex_lock: %s\n", strerror(ret));
return NULL;
}
puts("Mutex acquired in thread");
sleep(3600);
ret = pthread_mutex_unlock(&m);
if (ret)
fprintf(stderr, "pthread_mutex_unlock: %s\n", strerror(ret));
return NULL;
}int main(void)
{
struct sigaction sa;
pthread_t thread;
int ret = 0;
int main_ret = 0;
int destroy_mutex = 1;
sa.sa_handler = handle_sigint;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return EXIT_FAILURE;
}
pthread_mutex_init(&m, NULL);
ret = pthread_create(&thread, NULL, example_thread, NULL);
if (ret) {
fprintf(stderr, "pthread_create: %s\n", strerror(ret));
main_ret = ret;
goto out;
}
sleep(3);
printf("Wait for mutex in main thread, send INT to process %jd now\n", (intmax_t) getpid());
ret = pthread_mutex_lock(&m);
if (ret) {
fprintf(stderr, "pthread_mutex_lock: %s\n", strerror(ret));
main_ret = ret;
goto join;
}
puts("Got mutex in main thread");
ret = pthread_mutex_unlock(&m);
if (ret) {
fprintf(stderr, "pthread_mutex_unlock: %s\n", strerror(ret));
main_ret = ret;
destroy_mutex = 0;
goto join;
}join:
ret = pthread_join(thread, NULL);
if (ret) {
fprintf(stderr, "pthread_join: %s\n", strerror(ret));
return EXIT_FAILURE;
}out:
if (destroy_mutex) {
ret = pthread_mutex_destroy(&m);
if (ret) {
fprintf(stderr, "pthread_mutex_destroy: %s\n",
strerror(ret));
return EXIT_FAILURE;
}
}
return main_ret;
}Quoted 1 line
Arkadiusz, would you do the honours writing a patch? Should I?
I could do that but there is one more problem here - most pthread manpages mention EINTR only to say that they don't return it except pthread_cond_init.3:
$ find . -name "*pthread*" -print0 | xargs -0 grep EINTR ./man/man3/pthread_atfork.3:.BR EINTR . ./man/man3/pthread_tryjoin_np.3:.BR EINTR . ./man/man3/pthread_cond_init.3:\fBEINTR\fP ./man/man7/pthreads.7:.BR EINTR .
that says:
Quoted 1 line
EINTR
pthread_cond_timedwaitwas interrupted by a signal.
It's hard to say if it ever really worked like that because in man/man7/pthreads.7 it says that no EINTR has been the requirement since 2001:
Quoted 7 lines
Most pthreads functions return 0 on success, and an error number on failure. The error numbers that can be returned have the same meaning as the error numbers returned in errno by conventional system calls and C library functions. Note that the pthreads functions do not set errno. For each of the pthreads functions that can return an error, POSIX.1-2001 specifies that the function can never fail with the error EINTR.
Today POSIX still says that pthread_cond_timedwait() cannot return EINTR
https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_cond_clockwait.html
and in my experiments it really doesn't, both with glibc and musl.
pthread_cond_init.3 has been added in 1998 under linuxthread/ directory, maybe the behavior specified by POSIX was different than the initial design but OTOH it's quite surprising that no one noticed the bug for so many years, I wonder how many people still check for EINTR in pthread_cond_timedwait even though it's not necessary (but harmless).
So I was thinking that "pthread_mutex_lock(3), pthread_cond_wait(3), and related APIs." line could be completely removed from signal.7 because there is no pthread API that would return EINTR and EINTR should be removed from the list of valid error codes in pthread_cond_init.3. Does it sound good?
Signature · 1 line
Arkadiusz Drabczyk <[email protected]>
Alejandro Colomar
maintainerHi,
Quoted 1 line · Arkadiusz Drabczyk
Here is the minimal example for
pthread_mutex_lock()with all checks:
[...]
Thanks!
Quoted 7 lines
Quoted 1 line
Arkadiusz, would you do the honours writing a patch? Should I?
I could do that but there is one more problem here - most pthread manpages mention EINTR only to say that they don't return it except pthread_cond_init.3:
$ find . -name "*pthread*" -print0 | xargs -0 grep EINTR
In this repo, we respect POSIX recommendations. You can safely pass filenames to xargs(1) without needing -print0 or -0. :-)
$ find man/ -type f | grep pthread | xargs grep EINTR;
Quoted 26 lines
./man/man3/pthread_atfork.3:.BR EINTR .
./man/man3/pthread_tryjoin_np.3:.BR EINTR .
./man/man3/pthread_cond_init.3:\fBEINTR\fP
./man/man7/pthreads.7:.BR EINTR .that says:
Quoted 1 line
EINTR
pthread_cond_timedwaitwas interrupted by a signal.It's hard to say if it ever really worked like that because in man/man7/pthreads.7 it says that no EINTR has been the requirement since 2001:
Quoted 7 lines
Most pthreads functions return 0 on success, and an error number on failure. The error numbers that can be returned have the same meaning as the error numbers returned in errno by conventional system calls and C library functions. Note that the pthreads functions do not set errno. For each of the pthreads functions that can return an error, POSIX.1-2001 specifies that the function can never fail with the error EINTR.
Today POSIX still says that
pthread_cond_timedwait()cannot return EINTR
https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_cond_clockwait.html
and in my experiments it really doesn't, both with glibc and musl.
Quoted 6 lines
pthread_cond_init.3 has been added in 1998 under linuxthread/ directory, maybe the behavior specified by POSIX was different than the initial design but OTOH it's quite surprising that no one noticed the bug for so many years, I wonder how many people still check for EINTR in
pthread_cond_timedwaiteven though it's not necessary (but harmless).
The history of that manual page is quite complex.
$ git log --oneline --graph --abbrev --date=short --format='%h (%ad, %cd; "%s")' --follow -- man/man3/pthread_cond_init.3 | cat ... * ec557d3c5 (2024-11-13, 2024-11-13; "pthread_cond_init.3: tfix") ... * d88513fd7 (2024-06-16, 2024-06-16; "man/, share/mk/: srcfix, and remove corresponding XFAIL exceptions") ... * b841c4f3c (2024-05-19, 2024-05-19; "pthread_*.3: ffix") ... * dcde2f703 (2024-04-26, 2024-05-02; "man/, share/mk/: Move man*/ to man/") ... * ee7b1c770 (2024-02-26, 2024-02-26; "man3/: Say cancelation instead of cancellation") ... * c6d039a3a (2023-10-31, 2023-10-31; "man*/: srcfix (Use .P instead of .PP or .LP)") ... * 8a00cac75 (2023-10-04, 2023-10-04; "pthread_*.3: ffix (semantic newlines)") * 74235f157 (2023-10-03, 2023-10-04; "pthread_*.3: ffix (paragraphing)") * 13151ec52 (2023-10-03, 2023-10-04; "pthread_*.3: Remove AUTHOR section; add copyright; adapt TH") * c1c253d0e (2023-10-04, 2023-10-04; "pthread_cond_init.3, pthread_condattr_init.3, pthread_key_create.3, pthread_mutex_init.3, pthread_mutexattr_setkind_np.3, pthread_once.3: Update the glibc pages with the debian/glibc version of them") ... * a254db8b3 (2023-10-03, 2023-10-04; "pthread_cond_init.3, pthread_condattr_init.3, pthread_key_create.3, pthread_mutex_init.3, pthread_mutexattr_setkind_np.3, pthread_once.3: Import pages from glibc") * 87d09778d (2023-10-03, 2023-10-04; "Revert "linuxthreads, linuxthreads_db: Directories removed (preserved in ports repository)."") ... | * a3db24d46 (2005-07-03, 2023-10-03; "linuxthreads, linuxthreads_db: Directories removed (preserved in ports repository).") | * 2988d2724 (2004-12-22, 2023-10-03; "(CFLAGS-tst-align.c): Add -mpreferred-stack-boundary=4.") | * f7f73b1ca (2007-07-12, 2023-10-03; "2.5-18.1") ... | * 869af9b6a (1999-05-23, 2023-10-03; "Correct example.") | * 31b1e42d5 (1998-03-11, 2023-10-03; "LinuxThreads library.") |/
(There are a few commits that don't show up there, and I don't know how
to show them.)
Notice the difference between author dates and commit dates in the bottom 5 commits.
Those pages were removed in the glibc repository as obsolete in 2005. Debian imported them into a package, but they weren't maintained for a long time. At some point, I realized about their existence, and decided to import them, with all of their git gistory, into the Linux man-pages project, to start maintaining them, and eventually de-obsolete them (which as you can see, hasn't happened yet; I need people like you
reporting their problems). I imported them last year.
Quoted 5 lines
So I was thinking that "
pthread_mutex_lock(3),pthread_cond_wait(3), and related APIs." line could be completely removed from signal.7 because there is no pthread API that would return EINTR and EINTR should be removed from the list of valid error codes in pthread_cond_init.3. Does it sound good?
Sounds good. Please CC Florian and libc-help@ if you send a patch, so they can review.
Have a lovely night!
Alex
Signature · 1 line
<https://www.alejandro-colomar.es/>