Skip to contentSkip to conversation

signal(7): why does it say that pthread_mutex_lock() and thread_cond_wait() can fail with EINTR?

DISCUSSION Arkadiusz Drabczyk 3 participants 6 messages Jan 1, 2025 – Jan 3, 2025 Man-pages: manual pages for Linux -- Sections 2, 3, 4, 5, and 7

lore
Share & tools
lore
#0 lore raw

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_RESTART flag 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]>
#1 lore raw

[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_RESTART flag 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() nor pthread_cond_wait() return EINTR even if signal handler was installed without using SA_RESTART flag.

Please show some minimal examples if you can.

Quoted 3 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.

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/>
#2 lore raw

* 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

#3 lore raw

Hi 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/>
#4 lore raw
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_RESTART flag 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() nor pthread_cond_wait() return EINTR even if signal handler was installed without using SA_RESTART flag.

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_timedwait was 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]>
#5 lore raw

Hi,

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_timedwait was 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_timedwait even 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/>