This series of patches makes the 'term-style-control' module multithread-safe.
The original plan of redirecting the SIGCONT signal to the appropriate thread (from <https://lists.gnu.org/archive/html/bug-gnulib/2026-04/msg00034.html>) did not work: This redirect causes a delay, which for other signals is not important, but for SIGCONT it causes trouble (namely, the main thread may have progressed a lot before it receives the SIGCONT signal). Instead, the solution is to allow the SIGCONT handler to run in any thread. 2026-05-18 Bruno Haible <[email protected]> term-style-control: Make multithread-safe, part 5: SIGCONT handler. * lib/term-style-control.c (stopped_controller, stopped_user_data, stopped_signal_handler_needs_reinstall): New variables. (stopping_signal_handler): Set these variables. (continuing_signal_handler): Use these variables instead of active_controller and active_control_data, that may already be NULL at this point. (activate_term_style_controller): Initialize stopped_signal_handler_needs_reinstall. term-style-control: Make multithread-safe, part 4: Redirect signals. * lib/term-style-control.c (active_thread): New variable. (fatal_signal_handler, stopping_signal_handler): Redirect the signal from the current thread to the active_thread. (activate_term_non_default_mode): Initialize active_thread. (deactivate_term_non_default_mode): Reset active_thread to NULL. term-style-control: Make multithread-safe, part 3: Use sigdelay. * lib/term-style-control.h (struct term_style_control_data): Add field 'multithreaded'. * lib/term-style-control.c: Include thread-optim.h, sigdelay.h. (block_relevant_signals, unblock_relevant_signals): Add a 'multithreaded' parameter. (fatal_or_stopping_signal_handler, continuing_signal_handler, activate_term_non_default_mode, deactivate_term_non_default_mode): Update. (activate_term_style_controller): Determine whether the process is multithreaded. * modules/term-style-control (Depends-on): Add thread-optim, sigdelay. term-style-control: Make multithread-safe, part 2: Improve logging. * lib/term-style-control.c: Include <stdint.h>, <pthread.h>. (HAVE_POSIX_THREADS): New macro. (log_message): Preserve errno. (sprintf_integer_hex): New function. (log_signal_handler_called): Show also the current thread ID. (fatal_or_stopping_signal_handler): Add log_message invocations to show the taken code path. * modules/term-style-control (Depends-on): Add stdint-h. (configure.ac): Test for <pthread.h>. term-style-control: Make multithread-safe, part 1. * lib/term-style-control.h (activate_term_style_controller): Document a constraint regarding thread creation. (activate_term_non_default_mode): Document threading constraint. 2026-05-18 Bruno Haible <[email protected]> term-style-control tests: Add a test with multithreading. * tests/test-term-style-control-yes.h: New file, based on tests/test-term-style-control-yes.c. * tests/test-term-style-control-yes.c: Move most definitions to tests/test-term-style-control-yes.h. Include test-term-style-control-yes.h. (main): Just invoke styled_yes_loop. * tests/test-term-style-control-yes-mt.c: New file. * modules/term-style-control-tests (Files): Add tests/test-term-style-control-yes.h, tests/test-term-style-control-yes-mt.c. (Depends-on): Add thread, nanosleep. (Makefile.am): Arrange to build test-term-style-control-yes-mt.
>From b17a364d9146cc4d06e0f3f46eb71a3bdb354d5d Mon Sep 17 00:00:00 2001 From: Bruno Haible <[email protected]> Date: Mon, 18 May 2026 09:43:37 +0200 Subject: [PATCH 1/6] term-style-control tests: Add a test with multithreading. * tests/test-term-style-control-yes.h: New file, based on tests/test-term-style-control-yes.c. * tests/test-term-style-control-yes.c: Move most definitions to tests/test-term-style-control-yes.h. Include test-term-style-control-yes.h. (main): Just invoke styled_yes_loop. * tests/test-term-style-control-yes-mt.c: New file. * modules/term-style-control-tests (Files): Add tests/test-term-style-control-yes.h, tests/test-term-style-control-yes-mt.c. (Depends-on): Add thread, nanosleep. (Makefile.am): Arrange to build test-term-style-control-yes-mt. --- ChangeLog | 16 +++ modules/term-style-control-tests | 7 +- tests/test-term-style-control-yes-mt.c | 66 ++++++++++++ tests/test-term-style-control-yes.c | 115 +-------------------- tests/test-term-style-control-yes.h | 133 +++++++++++++++++++++++++ 5 files changed, 225 insertions(+), 112 deletions(-) create mode 100644 tests/test-term-style-control-yes-mt.c create mode 100644 tests/test-term-style-control-yes.h diff --git a/ChangeLog b/ChangeLog index 161534f35e..fe74cffec0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2026-05-18 Bruno Haible <[email protected]> + + term-style-control tests: Add a test with multithreading. + * tests/test-term-style-control-yes.h: New file, based on + tests/test-term-style-control-yes.c. + * tests/test-term-style-control-yes.c: Move most definitions to + tests/test-term-style-control-yes.h. + Include test-term-style-control-yes.h. + (main): Just invoke styled_yes_loop. + * tests/test-term-style-control-yes-mt.c: New file. + * modules/term-style-control-tests (Files): Add + tests/test-term-style-control-yes.h, + tests/test-term-style-control-yes-mt.c. + (Depends-on): Add thread, nanosleep. + (Makefile.am): Arrange to build test-term-style-control-yes-mt. + 2026-05-17 Bruno Haible <[email protected]> thread-optim: Update documentation. diff --git a/modules/term-style-control-tests b/modules/term-style-control-tests index 22ae8139eb..b1efef3e3e 100644 --- a/modules/term-style-control-tests +++ b/modules/term-style-control-tests @@ -1,17 +1,22 @@ Files: tests/test-term-style-control-hello.c tests/test-term-style-control-yes.c +tests/test-term-style-control-yes-mt.c +tests/test-term-style-control-yes.h Depends-on: bool unistd-h full-write +thread +nanosleep configure.ac: Makefile.am: TESTS += test-term-style-control-hello check_PROGRAMS += test-term-style-control-hello -noinst_PROGRAMS += test-term-style-control-yes +noinst_PROGRAMS += test-term-style-control-yes test-term-style-control-yes-mt test_term_style_control_hello_LDADD = $(LDADD) @LIBINTL@ $(LIBTHREAD) test_term_style_control_yes_LDADD = $(LDADD) @LIBINTL@ $(LIBTHREAD) +test_term_style_control_yes_mt_LDADD = $(LDADD) @LIBINTL@ $(LIBMULTITHREAD) $(NANOSLEEP_LIB) diff --git a/tests/test-term-style-control-yes-mt.c b/tests/test-term-style-control-yes-mt.c new file mode 100644 index 0000000000..e863883fce --- /dev/null +++ b/tests/test-term-style-control-yes-mt.c @@ -0,0 +1,66 @@ +/* Interactive test program for the term-style-control module. + Copyright (C) 2026 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Bruno Haible <[email protected]>, 2026. */ + +#include <config.h> + +/* Specification. */ +#include "term-style-control.h" + +#if USE_ISOC_THREADS || USE_POSIX_THREADS || USE_ISOC_AND_POSIX_THREADS || USE_WINDOWS_THREADS + +#include <time.h> +#include "glthread/thread.h" + +#include "test-term-style-control-yes.h" + +/* Thread that just sleeps and is ready to receive signals. */ +static void * +other_thread_func (_GL_UNUSED void *arg) +{ + for (;;) + { + struct timespec duration; + duration.tv_sec = 1; + duration.tv_nsec = 0; + nanosleep (&duration, NULL); + } +} + +int +main () +{ + /* Create another thread. */ + (void) gl_thread_create (other_thread_func, NULL); + + styled_yes_loop (); +} + +#else + +/* No multithreading available. */ + +#include <stdio.h> + +int +main () +{ + fputs ("Skipping test: multithreading not enabled\n", stderr); + return 77; +} + +#endif diff --git a/tests/test-term-style-control-yes.c b/tests/test-term-style-control-yes.c index 3f7fc382a6..11e96409c9 100644 --- a/tests/test-term-style-control-yes.c +++ b/tests/test-term-style-control-yes.c @@ -1,6 +1,5 @@ /* Interactive test program for the term-style-control module. Copyright (C) 2019-2026 Free Software Foundation, Inc. - Written by Bruno Haible <[email protected]>, 2019. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,123 +14,17 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ +/* Written by Bruno Haible <[email protected]>, 2019. */ + #include <config.h> /* Specification. */ #include "term-style-control.h" -#include <stdio.h> -#include <string.h> -#include <unistd.h> - -#include "full-write.h" - -/* This program outputs an endless amount of lines, each consisting of a - single 'y', in red color and underlined. - It can be used to exercise race conditions caused by - - simultaneous keyboard input on the terminal, - - pressing Ctrl-C, - - pressing Ctrl-Z and then "fg". */ - -/* ECMA-48 / ISO 6429 escape sequences. See - https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters - */ -static const char set_underline_on[] = "\033[4m"; -static const char set_underline_off[] = "\033[24m"; -static const char set_foreground_color_red[] = "\033[31m"; -static const char set_foreground_color_default[] = "\033[39m"; - -struct term_style_user_data -{ - /* This field is marked volatile, because it is accessed from the - async-safe function async_set_attributes_from_default. */ - bool volatile red_and_underline; - - struct term_style_control_data ctrl_data; -}; - -static struct term_style_control_data * -get_control_data (struct term_style_user_data *user_data) -{ - return &user_data->ctrl_data; -} - -static void -restore (_GL_UNUSED struct term_style_user_data *user_data) -{ - fputs (set_underline_off, stdout); - fputs (set_foreground_color_default, stdout); - fflush (stdout); -} - -static _GL_ASYNC_SAFE void -async_restore (_GL_UNUSED struct term_style_user_data *user_data) -{ - /* No <stdio.h> calls here! */ - full_write (STDOUT_FILENO, set_underline_off, - strlen (set_underline_off)); - full_write (STDOUT_FILENO, set_foreground_color_default, - strlen (set_foreground_color_default)); -} - -static _GL_ASYNC_SAFE void -async_set_attributes_from_default (struct term_style_user_data *user_data) -{ - /* No <stdio.h> calls here! */ - if (user_data->red_and_underline) - { - full_write (STDOUT_FILENO, set_underline_on, - strlen (set_underline_on)); - full_write (STDOUT_FILENO, set_foreground_color_red, - strlen (set_foreground_color_red)); - } -} - -static const struct term_style_controller controller = -{ - get_control_data, - restore, - async_restore, - async_set_attributes_from_default -}; +#include "test-term-style-control-yes.h" int main () { - struct term_style_user_data user_data; - - /* Initialization. */ - user_data.red_and_underline = false; - - activate_term_style_controller (&controller, &user_data, STDOUT_FILENO, - TTYCTL_AUTO); - - for (;;) - { - /* Before any styling, enable the non-default mode. */ - activate_term_non_default_mode (&controller, &user_data); - - /* Set user_data.red_and_underline *before* emitting the appropriate - escape sequences, otherwise async_set_attributes_from_default will not - do its job correctly. */ - user_data.red_and_underline = true; - fputs (set_underline_on, stdout); - fputs (set_foreground_color_red, stdout); - fflush (stdout); - - fputs ("y", stdout); - fflush (stdout); - - /* Revert to the default style before emitting a newline. */ - user_data.red_and_underline = false; - fputs (set_underline_off, stdout); - fputs (set_foreground_color_default, stdout); - fflush (stdout); - - /* Optional. */ - deactivate_term_non_default_mode (&controller, &user_data); - - fputs ("\n", stdout); - fflush (stdout); - } + styled_yes_loop (); } diff --git a/tests/test-term-style-control-yes.h b/tests/test-term-style-control-yes.h new file mode 100644 index 0000000000..74ed725a07 --- /dev/null +++ b/tests/test-term-style-control-yes.h @@ -0,0 +1,133 @@ +/* Interactive test program for the term-style-control module. + Copyright (C) 2019-2026 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Bruno Haible <[email protected]>, 2019. */ + +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "full-write.h" + +/* This program outputs an endless amount of lines, each consisting of a + single 'y', in red color and underlined. + It can be used to exercise race conditions caused by + - simultaneous keyboard input on the terminal, + - pressing Ctrl-C, + - pressing Ctrl-Z and then "fg". */ + +/* ECMA-48 / ISO 6429 escape sequences. See + https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters + */ +static const char set_underline_on[] = "\033[4m"; +static const char set_underline_off[] = "\033[24m"; +static const char set_foreground_color_red[] = "\033[31m"; +static const char set_foreground_color_default[] = "\033[39m"; + +struct term_style_user_data +{ + /* This field is marked volatile, because it is accessed from the + async-safe function async_set_attributes_from_default. */ + bool volatile red_and_underline; + + struct term_style_control_data ctrl_data; +}; + +static struct term_style_control_data * +get_control_data (struct term_style_user_data *user_data) +{ + return &user_data->ctrl_data; +} + +static void +restore (_GL_UNUSED struct term_style_user_data *user_data) +{ + fputs (set_underline_off, stdout); + fputs (set_foreground_color_default, stdout); + fflush (stdout); +} + +static _GL_ASYNC_SAFE void +async_restore (_GL_UNUSED struct term_style_user_data *user_data) +{ + /* No <stdio.h> calls here! */ + full_write (STDOUT_FILENO, set_underline_off, + strlen (set_underline_off)); + full_write (STDOUT_FILENO, set_foreground_color_default, + strlen (set_foreground_color_default)); +} + +static _GL_ASYNC_SAFE void +async_set_attributes_from_default (struct term_style_user_data *user_data) +{ + /* No <stdio.h> calls here! */ + if (user_data->red_and_underline) + { + full_write (STDOUT_FILENO, set_underline_on, + strlen (set_underline_on)); + full_write (STDOUT_FILENO, set_foreground_color_red, + strlen (set_foreground_color_red)); + } +} + +static const struct term_style_controller controller = +{ + get_control_data, + restore, + async_restore, + async_set_attributes_from_default +}; + +static void +styled_yes_loop (void) +{ + struct term_style_user_data user_data; + + /* Initialization. */ + user_data.red_and_underline = false; + + activate_term_style_controller (&controller, &user_data, STDOUT_FILENO, + TTYCTL_AUTO); + + for (;;) + { + /* Before any styling, enable the non-default mode. */ + activate_term_non_default_mode (&controller, &user_data); + + /* Set user_data.red_and_underline *before* emitting the appropriate + escape sequences, otherwise async_set_attributes_from_default will not + do its job correctly. */ + user_data.red_and_underline = true; + fputs (set_underline_on, stdout); + fputs (set_foreground_color_red, stdout); + fflush (stdout); + + fputs ("y", stdout); + fflush (stdout); + + /* Revert to the default style before emitting a newline. */ + user_data.red_and_underline = false; + fputs (set_underline_off, stdout); + fputs (set_foreground_color_default, stdout); + fflush (stdout); + + /* Optional. */ + deactivate_term_non_default_mode (&controller, &user_data); + + fputs ("\n", stdout); + fflush (stdout); + } +} -- 2.54.0
>From 99c6ed1b3310db1ed4c0fd37c4de865ced09831a Mon Sep 17 00:00:00 2001 From: Bruno Haible <[email protected]> Date: Mon, 18 May 2026 09:46:41 +0200 Subject: [PATCH 2/6] term-style-control: Make multithread-safe, part 1. * lib/term-style-control.h (activate_term_style_controller): Document a constraint regarding thread creation. (activate_term_non_default_mode): Document threading constraint. --- ChangeLog | 7 +++++++ lib/term-style-control.h | 11 +++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index fe74cffec0..45a66583ea 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2026-05-18 Bruno Haible <[email protected]> + + term-style-control: Make multithread-safe, part 1. + * lib/term-style-control.h (activate_term_style_controller): Document a + constraint regarding thread creation. + (activate_term_non_default_mode): Document threading constraint. + 2026-05-18 Bruno Haible <[email protected]> term-style-control tests: Add a test with multithreading. diff --git a/lib/term-style-control.h b/lib/term-style-control.h index b1af5852c3..75ac48ccbd 100644 --- a/lib/term-style-control.h +++ b/lib/term-style-control.h @@ -142,7 +142,11 @@ extern "C" { The effects of this functions are undone by calling deactivate_term_style_controller. You cannot have more than one controller activated at the same time. - You must not close FD while the controller is active. */ + You must not close FD while the controller is active. + The program must obey the following constraint: If at the moment of a + activate_term_style_controller() call the process is single-threaded, it + MUST NOT create additional threads until the matching + deactivate_term_style_controller() call. */ extern void activate_term_style_controller (const struct term_style_controller *controller, struct term_style_user_data *user_data, @@ -155,7 +159,10 @@ extern void This function is idempotent: When you call it twice in a row, the second invocation does nothing. The effects of this function are undone by calling - deactivate_term_non_default_mode. */ + deactivate_term_non_default_mode. + After calling this function in some thread, all output to the FD up to and + including the next deactivate_term_non_default_mode call must be done in + the same thread. */ extern void activate_term_non_default_mode (const struct term_style_controller *controller, struct term_style_user_data *user_data); -- 2.54.0
>From b2075253459fb21089cc6a79b4d7568708d1a6af Mon Sep 17 00:00:00 2001 From: Bruno Haible <[email protected]> Date: Mon, 18 May 2026 09:58:41 +0200 Subject: [PATCH 3/6] term-style-control: Make multithread-safe, part 2: Improve logging. * lib/term-style-control.c: Include <stdint.h>, <pthread.h>. (HAVE_POSIX_THREADS): New macro. (log_message): Preserve errno. (sprintf_integer_hex): New function. (log_signal_handler_called): Show also the current thread ID. (fatal_or_stopping_signal_handler): Add log_message invocations to show the taken code path. * modules/term-style-control (Depends-on): Add stdint-h. (configure.ac): Test for <pthread.h>. --- ChangeLog | 11 +++++++++++ lib/term-style-control.c | 38 +++++++++++++++++++++++++++++++++++++- modules/term-style-control | 2 ++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 45a66583ea..39db73a59a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,16 @@ 2026-05-18 Bruno Haible <[email protected]> + term-style-control: Make multithread-safe, part 2: Improve logging. + * lib/term-style-control.c: Include <stdint.h>, <pthread.h>. + (HAVE_POSIX_THREADS): New macro. + (log_message): Preserve errno. + (sprintf_integer_hex): New function. + (log_signal_handler_called): Show also the current thread ID. + (fatal_or_stopping_signal_handler): Add log_message invocations to show + the taken code path. + * modules/term-style-control (Depends-on): Add stdint-h. + (configure.ac): Test for <pthread.h>. + term-style-control: Make multithread-safe, part 1. * lib/term-style-control.h (activate_term_style_controller): Document a constraint regarding thread creation. diff --git a/lib/term-style-control.c b/lib/term-style-control.c index efa6d3a44d..8cc93c7175 100644 --- a/lib/term-style-control.c +++ b/lib/term-style-control.c @@ -31,6 +31,7 @@ #include <unistd.h> #if DEBUG_SIGNALS # include <stdio.h> +# include <stdint.h> #endif #if HAVE_TCGETATTR # include <termios.h> @@ -41,6 +42,10 @@ #if HAVE_TCGETATTR # include <sys/stat.h> #endif +#if HAVE_PTHREAD_H && !(defined _WIN32 && !defined __CYGWIN__) +# include <pthread.h> +# define HAVE_POSIX_THREADS 1 +#endif #include "fatal-signal.h" #include "sig-handler.h" @@ -102,7 +107,9 @@ nonintr_tcsetattr (int fd, int flush_mode, const struct termios *tcp) static _GL_ASYNC_SAFE void log_message (const char *message) { + int saved_errno = errno; full_write (STDERR_FILENO, message, strlen (message)); + errno = saved_errno; } #else @@ -162,6 +169,24 @@ simple_errno_string (char *str, int errnum) #if DEBUG_SIGNALS +/* Async-safe implementation of sprintf (str, "%jx", n). */ +static _GL_ASYNC_SAFE void +sprintf_integer_hex (char *str, uintmax_t x) +{ + char buf[40]; + char *p = buf + sizeof (buf); + do + { + unsigned int r = x % 16; + x = x / 16; + *--p = (r < 10 ? '0' + r : 'a' - 10 + r); + } + while (x > 0); + size_t n = buf + sizeof (buf) - p; + memcpy (str, p, n); + str[n] = '\0'; +} + /* Async-safe conversion of signal number to name. */ static _GL_ASYNC_SAFE void simple_signal_string (char *str, int sig) @@ -215,7 +240,12 @@ log_signal_handler_called (int sig) char message[100]; strcpy (message, "Signal handler for signal "); simple_signal_string (strnul (message), sig); - strcat (message, " called.\n"); + strcat (message, " called"); + #if HAVE_POSIX_THREADS + strcat (message, " in thread 0x"); + sprintf_integer_hex (strnul (message), (uintptr_t) pthread_self ()); + #endif + strcat (message, ".\n"); log_message (message); } @@ -653,6 +683,8 @@ fatal_or_stopping_signal_handler (int sig) if (active_controller != NULL && active_control_data->tty_control != TTYCTL_NONE) { + log_message ("In fatal_or_stopping_signal_handler: active controller.\n"); + /* Block the relevant signals. This is needed, because the output of escape sequences below (usually through tputs invocations) is not reentrant. */ @@ -673,6 +705,10 @@ fatal_or_stopping_signal_handler (int sig) /* Unblock the relevant signals. */ unblock_relevant_signals (); } + else + { + log_message ("In fatal_or_stopping_signal_handler: active_controller == NULL.\n"); + } #if HAVE_TCGETATTR if (echo_was_off) diff --git a/modules/term-style-control b/modules/term-style-control index 402318aa6d..fef6465c75 100644 --- a/modules/term-style-control +++ b/modules/term-style-control @@ -15,12 +15,14 @@ full-write fstat same-inode stdcountof-h +stdint-h strnul xalloc-die configure.ac: AC_REQUIRE([AC_C_INLINE]) gl_HAVE_TCGETATTR +AC_CHECK_HEADERS_ONCE([pthread.h]) Makefile.am: lib_SOURCES += term-style-control.c -- 2.54.0
>From 6b48af90b58cfaccc864456833a8e9cce3618a94 Mon Sep 17 00:00:00 2001 From: Bruno Haible <[email protected]> Date: Mon, 18 May 2026 10:17:08 +0200 Subject: [PATCH 4/6] term-style-control: Make multithread-safe, part 3: Use sigdelay. * lib/term-style-control.h (struct term_style_control_data): Add field 'multithreaded'. * lib/term-style-control.c: Include thread-optim.h, sigdelay.h. (block_relevant_signals, unblock_relevant_signals): Add a 'multithreaded' parameter. (fatal_or_stopping_signal_handler, continuing_signal_handler, activate_term_non_default_mode, deactivate_term_non_default_mode): Update. (activate_term_style_controller): Determine whether the process is multithreaded. * modules/term-style-control (Depends-on): Add thread-optim, sigdelay. --- ChangeLog | 13 +++++++++++++ lib/term-style-control.c | 39 ++++++++++++++++++++++++++------------ lib/term-style-control.h | 2 ++ modules/term-style-control | 2 ++ 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/ChangeLog b/ChangeLog index 39db73a59a..fee34c6373 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,18 @@ 2026-05-18 Bruno Haible <[email protected]> + term-style-control: Make multithread-safe, part 3: Use sigdelay. + * lib/term-style-control.h (struct term_style_control_data): Add field + 'multithreaded'. + * lib/term-style-control.c: Include thread-optim.h, sigdelay.h. + (block_relevant_signals, unblock_relevant_signals): Add a + 'multithreaded' parameter. + (fatal_or_stopping_signal_handler, continuing_signal_handler, + activate_term_non_default_mode, deactivate_term_non_default_mode): + Update. + (activate_term_style_controller): Determine whether the process is + multithreaded. + * modules/term-style-control (Depends-on): Add thread-optim, sigdelay. + term-style-control: Make multithread-safe, part 2: Improve logging. * lib/term-style-control.c: Include <stdint.h>, <pthread.h>. (HAVE_POSIX_THREADS): New macro. diff --git a/lib/term-style-control.c b/lib/term-style-control.c index 8cc93c7175..edb0ccc754 100644 --- a/lib/term-style-control.c +++ b/lib/term-style-control.c @@ -48,6 +48,8 @@ #endif #include "fatal-signal.h" +#include "thread-optim.h" +#include "sigdelay.h" #include "sig-handler.h" #include "full-write.h" #include "same-inode.h" @@ -596,23 +598,31 @@ init_relevant_signal_set () } } -/* Temporarily delay the relevant signals. */ +/* Temporarily delay the relevant signals. + This must be called only in the particular thread. */ static _GL_ASYNC_SAFE inline void -block_relevant_signals () +block_relevant_signals (bool multithreaded) { /* The caller must ensure that init_relevant_signal_set () was already called. */ if (!relevant_signal_set_initialized) abort (); - pthread_sigmask (SIG_BLOCK, &relevant_signal_set, NULL); + if (multithreaded) + sigdelay (SIG_BLOCK, &relevant_signal_set, NULL); + else + pthread_sigmask (SIG_BLOCK, &relevant_signal_set, NULL); } -/* Stop delaying the relevant signals. */ +/* Stop delaying the relevant signals. + This must be called only in the particular thread. */ static _GL_ASYNC_SAFE inline void -unblock_relevant_signals () +unblock_relevant_signals (bool multithreaded) { - pthread_sigmask (SIG_UNBLOCK, &relevant_signal_set, NULL); + if (multithreaded) + sigdelay (SIG_UNBLOCK, &relevant_signal_set, NULL); + else + pthread_sigmask (SIG_UNBLOCK, &relevant_signal_set, NULL); } #if defined SIGCONT @@ -685,10 +695,12 @@ fatal_or_stopping_signal_handler (int sig) { log_message ("In fatal_or_stopping_signal_handler: active controller.\n"); + bool mt = active_control_data->multithreaded; + /* Block the relevant signals. This is needed, because the output of escape sequences below (usually through tputs invocations) is not reentrant. */ - block_relevant_signals (); + block_relevant_signals (mt); /* Restore the terminal to the default state. */ for (unsigned int i = 0; i < 2; i++) @@ -703,7 +715,7 @@ fatal_or_stopping_signal_handler (int sig) #endif /* Unblock the relevant signals. */ - unblock_relevant_signals (); + unblock_relevant_signals (mt); } else { @@ -782,10 +794,12 @@ continuing_signal_handler (int sigcont) } } + bool mt = active_control_data->multithreaded; + /* Block the relevant signals. This is needed, because the output of escape sequences done inside the async_set_attributes_from_default call below is not reentrant. */ - block_relevant_signals (); + block_relevant_signals (mt); #if HAVE_TCGETATTR if (active_control_data->tty_control == TTYCTL_FULL) @@ -798,7 +812,7 @@ continuing_signal_handler (int sigcont) active_controller->async_set_attributes_from_default (active_user_data); /* Unblock the relevant signals. */ - unblock_relevant_signals (); + unblock_relevant_signals (mt); } errno = saved_errno; @@ -900,7 +914,7 @@ activate_term_non_default_mode (const struct term_style_controller *controller, /* Block fatal signals, so that a SIGINT or similar doesn't interrupt us without the possibility of restoring the terminal's state. Likewise for SIGTSTP etc. */ - block_relevant_signals (); + block_relevant_signals (control_data->multithreaded); #endif /* Enable the exit handler for restoring the terminal's state, @@ -963,7 +977,7 @@ deactivate_term_non_default_mode (const struct term_style_controller *controller #if BLOCK_SIGNALS_DURING_NON_DEFAULT_STYLE_OUTPUT /* Unblock the relevant signals. */ - unblock_relevant_signals (); + unblock_relevant_signals (control_data->multithreaded); #endif control_data->non_default_active = false; @@ -1003,6 +1017,7 @@ activate_term_style_controller (const struct term_style_controller *controller, /* This value is actually not used. */ control_data->same_as_stderr = false; #endif + control_data->multithreaded = gl_multithreaded (); control_data->non_default_active = false; diff --git a/lib/term-style-control.h b/lib/term-style-control.h index 75ac48ccbd..30bdeabaab 100644 --- a/lib/term-style-control.h +++ b/lib/term-style-control.h @@ -57,6 +57,8 @@ struct term_style_control_data #if HAVE_TCGETATTR bool volatile same_as_stderr; #endif + bool volatile multithreaded; /* True if the process is possibly + multithreaded. */ bool non_default_active; /* True if activate_term_non_default_mode() is in effect. */ }; diff --git a/modules/term-style-control b/modules/term-style-control index fef6465c75..402c558e4f 100644 --- a/modules/term-style-control +++ b/modules/term-style-control @@ -9,6 +9,8 @@ m4/tcgetattr.m4 Depends-on: bool fatal-signal +thread-optim +sigdelay pthread_sigmask sigaction full-write -- 2.54.0
>From 054e1d09180a5688cb9529e9eb8beff8829db962 Mon Sep 17 00:00:00 2001 From: Bruno Haible <[email protected]> Date: Mon, 18 May 2026 10:33:54 +0200 Subject: [PATCH 5/6] term-style-control: Make multithread-safe, part 4: Redirect signals. * lib/term-style-control.c (active_thread): New variable. (fatal_signal_handler, stopping_signal_handler): Redirect the signal from the current thread to the active_thread. (activate_term_non_default_mode): Initialize active_thread. (deactivate_term_non_default_mode): Reset active_thread to NULL. --- ChangeLog | 7 ++++++ lib/term-style-control.c | 46 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index fee34c6373..ab4d2db3cf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,12 @@ 2026-05-18 Bruno Haible <[email protected]> + term-style-control: Make multithread-safe, part 4: Redirect signals. + * lib/term-style-control.c (active_thread): New variable. + (fatal_signal_handler, stopping_signal_handler): Redirect the signal + from the current thread to the active_thread. + (activate_term_non_default_mode): Initialize active_thread. + (deactivate_term_non_default_mode): Reset active_thread to NULL. + term-style-control: Make multithread-safe, part 3: Use sigdelay. * lib/term-style-control.h (struct term_style_control_data): Add field 'multithreaded'. diff --git a/lib/term-style-control.c b/lib/term-style-control.c index edb0ccc754..4fbf67d491 100644 --- a/lib/term-style-control.c +++ b/lib/term-style-control.c @@ -441,6 +441,12 @@ static struct term_style_control_data * volatile active_control_data; : -1). */ static int volatile active_fd = -1; +#if HAVE_POSIX_THREADS +/* The thread to which the active controller is attached, (pthread_t) 0 + otherwise. */ +static pthread_t volatile active_thread; +#endif + /* The exit handler. */ static void atexit_handler (void) @@ -734,7 +740,22 @@ static _GL_ASYNC_SAFE void fatal_signal_handler (int sig) { log_signal_handler_called (sig); - fatal_or_stopping_signal_handler (sig); + #if HAVE_POSIX_THREADS + pthread_t target_thread = active_thread; + if (target_thread != pthread_self ()) + { + if (target_thread != (pthread_t) 0) + { + pthread_kill (target_thread, sig); + return; + } + log_message ("target_thread is NULL. Not calling fatal_or_stopping_signal_handler!\n"); + } + else + #endif + { + fatal_or_stopping_signal_handler (sig); + } } #if defined SIGCONT @@ -747,7 +768,22 @@ stopping_signal_handler (int sig) int saved_errno = errno; log_signal_handler_called (sig); - fatal_or_stopping_signal_handler (sig); + #if HAVE_POSIX_THREADS + pthread_t target_thread = active_thread; + if (target_thread != pthread_self ()) + { + if (target_thread != (pthread_t) 0) + { + pthread_kill (target_thread, sig); + return; + } + log_message ("target_thread is NULL. Not calling fatal_or_stopping_signal_handler!\n"); + } + else + #endif + { + fatal_or_stopping_signal_handler (sig); + } /* Now execute the signal's default action. We reinstall the handler later, during the SIGCONT handler. */ @@ -929,6 +965,9 @@ activate_term_non_default_mode (const struct term_style_controller *controller, we set active_controller to a non-NULL value only after the memory locations active_user_data, active_control_data, active_fd have been filled. */ + #if HAVE_POSIX_THREADS + active_thread = pthread_self (); + #endif active_fd = control_data->fd; active_control_data = control_data; active_user_data = user_data; @@ -974,6 +1013,9 @@ deactivate_term_non_default_mode (const struct term_style_controller *controller active_user_data = NULL; active_control_data = NULL; active_fd = -1; + #if HAVE_POSIX_THREADS + active_thread = (pthread_t) 0; + #endif #if BLOCK_SIGNALS_DURING_NON_DEFAULT_STYLE_OUTPUT /* Unblock the relevant signals. */ -- 2.54.0
>From 4ba048396fbd9e6e4d7deae90c482be88e53a2a8 Mon Sep 17 00:00:00 2001 From: Bruno Haible <[email protected]> Date: Mon, 18 May 2026 10:40:20 +0200 Subject: [PATCH 6/6] term-style-control: Make multithread-safe, part 5: SIGCONT handler. * lib/term-style-control.c (stopped_controller, stopped_user_data, stopped_signal_handler_needs_reinstall): New variables. (stopping_signal_handler): Set these variables. (continuing_signal_handler): Use these variables instead of active_controller and active_control_data, that may already be NULL at this point. (activate_term_style_controller): Initialize stopped_signal_handler_needs_reinstall. --- ChangeLog | 10 ++++++ lib/term-style-control.c | 70 ++++++++++++++++++++++++++++------------ 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/ChangeLog b/ChangeLog index ab4d2db3cf..6ec8724e19 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,15 @@ 2026-05-18 Bruno Haible <[email protected]> + term-style-control: Make multithread-safe, part 5: SIGCONT handler. + * lib/term-style-control.c (stopped_controller, stopped_user_data, + stopped_signal_handler_needs_reinstall): New variables. + (stopping_signal_handler): Set these variables. + (continuing_signal_handler): Use these variables instead of + active_controller and active_control_data, that may already be NULL + at this point. + (activate_term_style_controller): Initialize + stopped_signal_handler_needs_reinstall. + term-style-control: Make multithread-safe, part 4: Redirect signals. * lib/term-style-control.c (active_thread): New variable. (fatal_signal_handler, stopping_signal_handler): Redirect the signal diff --git a/lib/term-style-control.c b/lib/term-style-control.c index 4fbf67d491..c6f904f0c9 100644 --- a/lib/term-style-control.c +++ b/lib/term-style-control.c @@ -760,6 +760,12 @@ fatal_signal_handler (int sig) #if defined SIGCONT +/* Information from the stopping_signal_handler, for use by the + continuing_signal_handler. */ +static const struct term_style_controller * volatile stopped_controller; +static struct term_style_user_data * volatile stopped_user_data; +static bool volatile stopped_signal_handler_needs_reinstall; + /* The signal handler for stopping signals. It is reentrant. */ static _GL_ASYNC_SAFE void @@ -785,6 +791,10 @@ stopping_signal_handler (int sig) fatal_or_stopping_signal_handler (sig); } + /* Prepare for the invocation of the continuing_signal_handler. */ + stopped_controller = active_controller; + stopped_user_data = active_user_data; + /* Now execute the signal's default action. We reinstall the handler later, during the SIGCONT handler. */ { @@ -792,7 +802,10 @@ stopping_signal_handler (int sig) action.sa_handler = SIG_DFL; action.sa_flags = SA_NODEFER; sigemptyset (&action.sa_mask); - sigaction (sig, &action, NULL); + struct sigaction old_action; + sigaction (sig, &action, &old_action); + stopped_signal_handler_needs_reinstall = + (old_action.sa_handler == stopping_signal_handler); } errno = saved_errno; raise (sig); @@ -806,10 +819,15 @@ continuing_signal_handler (int sigcont) int saved_errno = errno; log_signal_handler_called (sigcont); + + /* Using pthread_kill to forward the signal to the active_thread does + not produce good results, as this forwarding takes some time and the + active_thread is already running during that time. Therefore, + process the SIGGONT signal in whatever thread that received it. */ + update_pgrp_status (); - /* Only do something while some output was interrupted. */ - if (active_controller != NULL - && active_control_data->tty_control != TTYCTL_NONE) + + if (stopped_signal_handler_needs_reinstall) { /* Reinstall the signals handlers removed in stopping_signal_handler. */ for (unsigned int i = 0; i < num_job_control_signals; i++) @@ -829,26 +847,37 @@ continuing_signal_handler (int sigcont) sigaction (sig, &action, NULL); } } + } - bool mt = active_control_data->multithreaded; + /* Only do something while some output was interrupted. */ + const struct term_style_controller *controller = stopped_controller; + if (controller != NULL) + { + struct term_style_user_data *user_data = stopped_user_data; + struct term_style_control_data *control_data = + controller->get_control_data (user_data); + if (control_data != NULL && control_data->tty_control != TTYCTL_NONE) + { + bool mt = control_data->multithreaded; - /* Block the relevant signals. This is needed, because the output of - escape sequences done inside the async_set_attributes_from_default - call below is not reentrant. */ - block_relevant_signals (mt); + /* Block the relevant signals. This is needed, because the output of + escape sequences done inside the async_set_attributes_from_default + call below is not reentrant. */ + block_relevant_signals (mt); - #if HAVE_TCGETATTR - if (active_control_data->tty_control == TTYCTL_FULL) - { - /* Modify the local mode. */ - clobber_local_mode (); - } - #endif - /* Set the terminal attributes. */ - active_controller->async_set_attributes_from_default (active_user_data); + #if HAVE_TCGETATTR + if (control_data->tty_control == TTYCTL_FULL) + { + /* Modify the local mode. */ + clobber_local_mode (); + } + #endif + /* Set the terminal attributes. */ + controller->async_set_attributes_from_default (user_data); - /* Unblock the relevant signals. */ - unblock_relevant_signals (mt); + /* Unblock the relevant signals. */ + unblock_relevant_signals (mt); + } } errno = saved_errno; @@ -1066,6 +1095,7 @@ activate_term_style_controller (const struct term_style_controller *controller, /* Start keeping track of the process group status. */ term_fd = fd; #if defined SIGCONT + stopped_signal_handler_needs_reinstall = false; ensure_continuing_signal_handler (); #endif update_pgrp_status (); -- 2.54.0
