aboutsummaryrefslogtreecommitdiffstats
path: root/src/process.c
diff options
context:
space:
mode:
authorPhilipp Stephani2021-01-10 16:31:12 +0100
committerPhilipp Stephani2021-01-16 19:46:44 +0100
commit8f0ce42d3eb9b212424a4a25a376287ffc94a73e (patch)
treeb05a83f36c0617184079a9fa5ce33a945e7e736e /src/process.c
parent66756df286bea6efd3f9a8290e38e8d77bdf0264 (diff)
downloademacs-8f0ce42d3eb9b212424a4a25a376287ffc94a73e.tar.gz
emacs-8f0ce42d3eb9b212424a4a25a376287ffc94a73e.zip
Fix deadlock when receiving SIGCHLD during 'pselect'.
If we receive and handle a SIGCHLD signal for a process while waiting for that process, 'pselect' might never return. Instead, we have to explicitly 'pselect' that the process status has changed. We do this by writing to a pipe in the SIGCHLD handler and having 'wait_reading_process_output' select on it. * src/process.c (child_signal_init): New helper function to create a pipe for SIGCHLD notifications. (child_signal_read, child_signal_notify): New helper functions to read from/write to the child signal pipe. (create_process): Initialize the child signal pipe on first use. (handle_child_signal): Notify waiters that a process status has changed. (wait_reading_process_output): Make sure that we also catch SIGCHLD/process status changes. * test/src/process-tests.el (process-tests/fd-setsize-no-crash/make-process): Remove workaround, which is no longer needed.
Diffstat (limited to 'src/process.c')
-rw-r--r--src/process.c94
1 files changed, 93 insertions, 1 deletions
diff --git a/src/process.c b/src/process.c
index dac7d0440fa..474c87089e0 100644
--- a/src/process.c
+++ b/src/process.c
@@ -283,6 +283,16 @@ static int max_desc;
283 the file descriptor of a socket that is already bound. */ 283 the file descriptor of a socket that is already bound. */
284static int external_sock_fd; 284static int external_sock_fd;
285 285
286/* File descriptor that becomes readable when we receive SIGCHLD. */
287static int child_signal_read_fd = -1;
288/* The write end thereof. The SIGCHLD handler writes to this file
289 descriptor to notify `wait_reading_process_output' of process
290 status changes. */
291static int child_signal_write_fd = -1;
292static void child_signal_init (void);
293static void child_signal_read (int, void *);
294static void child_signal_notify (void);
295
286/* Indexed by descriptor, gives the process (if any) for that descriptor. */ 296/* Indexed by descriptor, gives the process (if any) for that descriptor. */
287static Lisp_Object chan_process[FD_SETSIZE]; 297static Lisp_Object chan_process[FD_SETSIZE];
288static void wait_for_socket_fds (Lisp_Object, char const *); 298static void wait_for_socket_fds (Lisp_Object, char const *);
@@ -2060,6 +2070,10 @@ create_process (Lisp_Object process, char **new_argv, Lisp_Object current_dir)
2060 Lisp_Object lisp_pty_name = Qnil; 2070 Lisp_Object lisp_pty_name = Qnil;
2061 sigset_t oldset; 2071 sigset_t oldset;
2062 2072
2073 /* Ensure that the SIGCHLD handler can notify
2074 `wait_reading_process_output'. */
2075 child_signal_init ();
2076
2063 inchannel = outchannel = -1; 2077 inchannel = outchannel = -1;
2064 2078
2065 if (p->pty_flag) 2079 if (p->pty_flag)
@@ -5395,6 +5409,14 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd,
5395 check_write = true; 5409 check_write = true;
5396 } 5410 }
5397 5411
5412 /* We have to be informed when we receive a SIGCHLD signal for
5413 an asynchronous process. Otherwise this might deadlock if we
5414 receive a SIGCHLD during `pselect'. */
5415 int child_fd = child_signal_read_fd;
5416 eassert (0 <= child_fd);
5417 eassert (child_fd < FD_SETSIZE);
5418 FD_SET (child_fd, &Available);
5419
5398 /* If frame size has changed or the window is newly mapped, 5420 /* If frame size has changed or the window is newly mapped,
5399 redisplay now, before we start to wait. There is a race 5421 redisplay now, before we start to wait. There is a race
5400 condition here; if a SIGIO arrives between now and the select 5422 condition here; if a SIGIO arrives between now and the select
@@ -7114,7 +7136,70 @@ process has been transmitted to the serial port. */)
7114 subprocesses which the main thread should not reap. For example, 7136 subprocesses which the main thread should not reap. For example,
7115 if the main thread attempted to reap an already-reaped child, it 7137 if the main thread attempted to reap an already-reaped child, it
7116 might inadvertently reap a GTK-created process that happened to 7138 might inadvertently reap a GTK-created process that happened to
7117 have the same process ID. */ 7139 have the same process ID.
7140
7141 To avoid a deadlock when receiving SIGCHLD while
7142 `wait_reading_process_output' is in `pselect', the SIGCHLD handler
7143 will notify the `pselect' using a pipe. */
7144
7145/* Set up `child_signal_read_fd' and `child_signal_write_fd'. */
7146
7147static void
7148child_signal_init (void)
7149{
7150 /* Either both are initialized, or both are uninitialized. */
7151 eassert ((child_signal_read_fd < 0) == (child_signal_write_fd < 0));
7152
7153 if (0 <= child_signal_read_fd)
7154 return; /* already done */
7155
7156 int fds[2];
7157 if (emacs_pipe (fds) < 0)
7158 report_file_error ("Creating pipe for child signal", Qnil);
7159 if (FD_SETSIZE <= fds[0])
7160 {
7161 /* Since we need to `pselect' on the read end, it has to fit
7162 into an `fd_set'. */
7163 emacs_close (fds[0]);
7164 emacs_close (fds[1]);
7165 report_file_errno ("Creating pipe for child signal", Qnil,
7166 EMFILE);
7167 }
7168
7169 /* We leave the file descriptors open until the Emacs process
7170 exits. */
7171 eassert (0 <= fds[0]);
7172 eassert (0 <= fds[1]);
7173 add_read_fd (fds[0], child_signal_read, NULL);
7174 fd_callback_info[fds[0]].flags &= ~KEYBOARD_FD;
7175 child_signal_read_fd = fds[0];
7176 child_signal_write_fd = fds[1];
7177}
7178
7179/* Consume a process status change. */
7180
7181static void
7182child_signal_read (int fd, void *data)
7183{
7184 eassert (0 <= fd);
7185 eassert (fd == child_signal_read_fd);
7186 char dummy;
7187 if (emacs_read (fd, &dummy, 1) < 0)
7188 emacs_perror ("reading from child signal FD");
7189}
7190
7191/* Notify `wait_reading_process_output' of a process status
7192 change. */
7193
7194static void
7195child_signal_notify (void)
7196{
7197 int fd = child_signal_write_fd;
7198 eassert (0 <= fd);
7199 char dummy = 0;
7200 if (emacs_write (fd, &dummy, 1) != 1)
7201 emacs_perror ("writing to child signal FD");
7202}
7118 7203
7119/* LIB_CHILD_HANDLER is a SIGCHLD handler that Emacs calls while doing 7204/* LIB_CHILD_HANDLER is a SIGCHLD handler that Emacs calls while doing
7120 its own SIGCHLD handling. On POSIXish systems, glib needs this to 7205 its own SIGCHLD handling. On POSIXish systems, glib needs this to
@@ -7152,6 +7237,7 @@ static void
7152handle_child_signal (int sig) 7237handle_child_signal (int sig)
7153{ 7238{
7154 Lisp_Object tail, proc; 7239 Lisp_Object tail, proc;
7240 bool changed = false;
7155 7241
7156 /* Find the process that signaled us, and record its status. */ 7242 /* Find the process that signaled us, and record its status. */
7157 7243
@@ -7174,6 +7260,7 @@ handle_child_signal (int sig)
7174 eassert (ok); 7260 eassert (ok);
7175 if (child_status_changed (deleted_pid, 0, 0)) 7261 if (child_status_changed (deleted_pid, 0, 0))
7176 { 7262 {
7263 changed = true;
7177 if (STRINGP (XCDR (head))) 7264 if (STRINGP (XCDR (head)))
7178 unlink (SSDATA (XCDR (head))); 7265 unlink (SSDATA (XCDR (head)));
7179 XSETCAR (tail, Qnil); 7266 XSETCAR (tail, Qnil);
@@ -7191,6 +7278,7 @@ handle_child_signal (int sig)
7191 && child_status_changed (p->pid, &status, WUNTRACED | WCONTINUED)) 7278 && child_status_changed (p->pid, &status, WUNTRACED | WCONTINUED))
7192 { 7279 {
7193 /* Change the status of the process that was found. */ 7280 /* Change the status of the process that was found. */
7281 changed = true;
7194 p->tick = ++process_tick; 7282 p->tick = ++process_tick;
7195 p->raw_status = status; 7283 p->raw_status = status;
7196 p->raw_status_new = 1; 7284 p->raw_status_new = 1;
@@ -7210,6 +7298,10 @@ handle_child_signal (int sig)
7210 } 7298 }
7211 } 7299 }
7212 7300
7301 if (changed)
7302 /* Wake up `wait_reading_process_output'. */
7303 child_signal_notify ();
7304
7213 lib_child_handler (sig); 7305 lib_child_handler (sig);
7214#ifdef NS_IMPL_GNUSTEP 7306#ifdef NS_IMPL_GNUSTEP
7215 /* NSTask in GNUstep sets its child handler each time it is called. 7307 /* NSTask in GNUstep sets its child handler each time it is called.