Skip to content

Instantly share code, notes, and snippets.

@alphapapa
Last active August 18, 2021 13:09
Show Gist options
  • Save alphapapa/daa36dc3388dec694602e06fe6d114b7 to your computer and use it in GitHub Desktop.
Save alphapapa/daa36dc3388dec694602e06fe6d114b7 to your computer and use it in GitHub Desktop.
An ECM for a possible bug in Emacs's process-status code
;;; emacs-process-status-sentinel-ecm.el --- -*- lexical-binding: t; -*-
;; This file provides an ECM for a possible bug in Emacs's process
;; handling. The rub is whether a process sentinel may be called with
;; a STATUS string of "finished\n" while the process's
;; `process-status' is `open': If that is expected, then this may not
;; indicate a bug (but perhaps an issue that needs to be more
;; prominently documented). But if it's not supposed to happen, then
;; this may provide a means to reproduce the behavior and troubleshoot
;; the bug.
;; This ECM works by making 10 (by default) curl `pipe' processes that
;; send requests to httpbin.org. When each process is finished, the
;; process sentinel should conclude by killing the process's buffer.
;; This appears to work most of the time, especially with low numbers
;; of processes (like 1-3). But as the number of processes is
;; increased, sometimes one or more of the attempts to kill a
;; process's buffer result in the user's being prompted to kill the
;; buffer due to the process still running (an obvious problem when
;; happening in library code that the user should not be concerned
;; with). As the number of processes increases, the number of process
;; buffers that prompt seems to increase (e.g. with 15 processes, 1-2
;; prompts seems common).
;; A possible workaround would be to check the process's status before
;; attempting to kill the buffer, however that would seem to raise a
;; problem: according to the Elisp manual, calls to the sentinel may
;; be coalesced when a process's status changes quickly, so if the
;; sentinel simply did not kill the buffer if the process were still
;; alive, it's unclear whether the sentinel will be called a final
;; time, after the process's status is `closed'; if not, the buffer
;; would never be killed. Alternatively, the sentinel could loop
;; while checking `process-status', but that would obviously be
;; undesirable, and would seem like working around a bug (and it might
;; be the wrong thing to do in general, depending on the process's and
;; sentinel's purposes).
;; To test this ECM:
;; 1. Evaluate this file (with lexical-binding).
;; 2. Eval (argh).
;; 3. The messages buffer should show the first line of the HTTP
;; response body for requests 1-10, as well as a "killing buffer..."
;; line for each curl process's buffer. There should be NO "Buffer
;; has a running process; kill it?" prompts.
;; 4. But, usually, there will be one or two of those prompts. When
;; there are, you can see that the buffer's process has a
;; (process-status) value of "open" rather than "closed", even though
;; the process's sentinel was called with "finished\n" as the status
;; argument.
;; So far, this has been reproduced on Emacs 26.3, 27.2, and 28.0.50
;; (built on 2021-07-05), on GNU/Linux.
(require 'cl-lib)
(require 'rx)
(defvar-local argh-callback nil)
(defun argh ()
(let* ((i 0))
(while (< i 10)
(setq i (1+ i))
(let ((prefix (format "%s" i)))
(message "%S"
(argh-curl i (lambda (data)
(message "%s: %S" prefix data))))))))
(defun argh-curl (num callback)
(let* ((process-buffer (generate-new-buffer (format "argh-curl-%02d" num)))
(process (make-process :name "argh-curl"
:buffer process-buffer
:coding 'binary
:command '("curl"
"--silent"
"--compressed"
"--location"
"--dump-header" "-"
"--config" "-")
:connection-type 'pipe
:sentinel #'argh--sentinel
:stderr process-buffer))
(curl-config "--url https://httpbin.org/get\n"))
(with-current-buffer process-buffer
(setf argh-callback callback))
(process-send-string process curl-config)
(process-send-eof process)
process))
(defun argh--sentinel (process-or-buffer status)
(let ((buffer (cl-typecase process-or-buffer
(buffer process-or-buffer)
(process (process-buffer process-or-buffer)))))
(unwind-protect
(with-current-buffer buffer
(pcase-exhaustive status
("finished\n"
;; Curl exited normally.
(goto-char (point-min))
(re-search-forward "^\r\n" nil)
(forward-line 1)
(funcall argh-callback (buffer-substring (point-at-bol) (point-at-eol))))
((or (and (pred numberp) code)
(rx "exited abnormally with code " (let code (group (1+ digit)))))
;; Curl error.
nil)
("killed\n"
;; Curl process killed.
nil)))
(pcase status
((or "finished\n"
"killed\n"
(pred numberp)
(rx "exited abnormally with code " (1+ digit)))
(message "killing buffer %S of process. PROCESS-STATUS:%S SENTINEL-STATUS-ARG:%S"
buffer (process-status (get-buffer-process buffer)) status)
(kill-buffer buffer))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment