diff options
| author | Philipp Stephani | 2021-01-10 16:31:12 +0100 |
|---|---|---|
| committer | Philipp Stephani | 2021-01-16 19:46:44 +0100 |
| commit | 8f0ce42d3eb9b212424a4a25a376287ffc94a73e (patch) | |
| tree | b05a83f36c0617184079a9fa5ce33a945e7e736e /src/process.c | |
| parent | 66756df286bea6efd3f9a8290e38e8d77bdf0264 (diff) | |
| download | emacs-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.c | 94 |
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. */ |
| 284 | static int external_sock_fd; | 284 | static int external_sock_fd; |
| 285 | 285 | ||
| 286 | /* File descriptor that becomes readable when we receive SIGCHLD. */ | ||
| 287 | static 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. */ | ||
| 291 | static int child_signal_write_fd = -1; | ||
| 292 | static void child_signal_init (void); | ||
| 293 | static void child_signal_read (int, void *); | ||
| 294 | static 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. */ |
| 287 | static Lisp_Object chan_process[FD_SETSIZE]; | 297 | static Lisp_Object chan_process[FD_SETSIZE]; |
| 288 | static void wait_for_socket_fds (Lisp_Object, char const *); | 298 | static 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 | |||
| 7147 | static void | ||
| 7148 | child_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 | |||
| 7181 | static void | ||
| 7182 | child_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 | |||
| 7194 | static void | ||
| 7195 | child_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 | |||
| 7152 | handle_child_signal (int sig) | 7237 | handle_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. |