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

Reply via email to