Last active
August 18, 2021 13:09
-
-
Save alphapapa/daa36dc3388dec694602e06fe6d114b7 to your computer and use it in GitHub Desktop.
An ECM for a possible bug in Emacs's process-status code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; 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