Subject: bug#68792: Process sentinels are sometimes not called when inotify is used

From: Bryan Joseph Tabor <[email-masked]>
Date: Mon, 29 Jan 2024 05:15:51 +0000

Tags: patch

Hi, to reproduce this issue please save the following elisp to proc.el:

        (defvar p-stopped nil)

        (defun my-sentinel (proc str)
          (when (string-match "^\\(?:finished\n\\|exited abnormally\\|killed\n\\)"
                              str)
            (setq p-stopped t)))

        (defun wait-for-process-end (proc)
          (while (not p-stopped)
            (message "process status is: %s" (process-status proc))
            (accept-process-output proc 0.1 nil t)))

        (defun f ()
          (with-temp-buffer
          (inotify-add-watch "foo.txt" t #'ignore)
          (let ((proc (start-process "p" (current-buffer) "bash" "-c" "touch foo.txt")))
            (setq p-stopped nil)
            (set-process-sentinel proc 'my-sentinel)
            (wait-for-process-end proc))))

Please also create a file 'foo.txt'

        $ touch foo.txt

Now, invoke the following command

        $ emacs -Q --script proc.el --eval "(f)"

This command will never exit, and print "process status is: exit" forever.

The expected behavior is that, because process "p" is exited, its sentinel should be called, so 'wait-for-process-end' will complete and Emacs will exit.

After some investigation, I found that 'wait_reading_process_output' in process.c never calls the sentinel for the process because the pselect invocation in this code (src/process.c) always returns nonzero:

        if ((thread_select (pselect, max_desc + 1,
                              &Atemp,
                              (num_pending_connects > 0 ? &Ctemp : NULL),
                              NULL, &timeout, NULL)
                   <= 0))
                {
                  /* It's okay for us to do this and then continue with
                   the loop, since timeout has already been zeroed out.  */
                  clear_waiting_for_input ();
                  got_some_output = status_notify (NULL, wait_proc);
                  if (do_display) redisplay_preserve_echo_area (13);
                }

This pselect invocation always returns nonzero because the inotify fd for "foo.txt" is included in the pselect readfds and is readable. However, the inotify fd is never handled within 'wait_reading_process_output'.

If I understand this code correctly, the intent here is to prioritize handling ready fds before calling 'status_notify', which invokes sentinels. However, it seems inotify fds are not handled in 'wait_reading_process_output', so there is no reason to care about inotify fds here. I believe the correct fix is to filter for process fds in this pselect call. To accomplish this, the included patch filters for process fds in 'compute_input_wait_mask'. It appears to me that all usage of 'compute_input_wait_mask' only cares about process fds, so I believe this is a safe change.

In GNU Emacs 30.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version
 3.24.38, cairo version 1.17.8) of 2023-12-23 built on ostrich
Repository revision: 5c3ff1494b69bf45b99125f2423174222badfa43
Repository branch: master
Windowing system distributor 'The X.Org Foundation', version 11.0.12101011
System Description: Arch Linux

Configured using:
 'configure --prefix=/usr --sysconfdir=/etc --libexecdir=/usr/lib
 --localstatedir=/var --mandir=/usr/share/man --with-gameuser=:games
 --with-modules --without-m17n-flt --without-gconf
 --with-native-compilation=yes --with-xinput2 --with-x-toolkit=gtk3
 --without-xaw3d --with-sound=no --with-tree-sitter --without-gpm
 --without-compress-install
 '--program-transform-name=s/\([ec]tags\)/\1.emacs/'
 'CFLAGS=-march=x86-64 -mtune=generic -O2 -pipe -fno-plt -fexceptions
 -Wp,-D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security
 -fstack-clash-protection -fcf-protection'
 LDFLAGS=-Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now'
From 281f2a2da9b5ce7cc9c9029be99801e923dcbf24 Mon Sep 17 00:00:00 2001
From: Bryan Tabor <[email-masked]>
Date: Sun, 28 Jan 2024 23:36:04 -0500
Subject: [PATCH] * process.c: Fix process sentinels not called when inotify
 used

---
 src/process.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/process.c b/src/process.c
index ddab9ed6c01..41cc8a0caa5 100644
--- a/src/process.c
+++ b/src/process.c
@@ -597,7 +597,8 @@ compute_input_wait_mask (fd_set *mask)
       if (fd_callback_info[fd].waiting_thread != NULL
 	  && fd_callback_info[fd].waiting_thread != current_thread)
 	continue;
-      if ((fd_callback_info[fd].flags & FOR_READ) != 0)
+      if ((fd_callback_info[fd].flags & (FOR_READ | PROCESS_FD))
+	  == (FOR_READ | PROCESS_FD))
 	{
 	  FD_SET (fd, mask);
 	  fd_callback_info[fd].waiting_thread = current_thread;
-- 
2.43.0