aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJim Porter2024-07-06 14:09:08 -0700
committerJim Porter2024-07-09 17:28:32 -0700
commit8e46f44ea0eb761e24beda8c5cdbc8fcca87307a (patch)
tree9b9056b88b716f82e926a2a3896695b9ea11b65d
parent342998511add79c594a170dc04ecda2f2db0fd36 (diff)
downloademacs-8e46f44ea0eb761e24beda8c5cdbc8fcca87307a.tar.gz
emacs-8e46f44ea0eb761e24beda8c5cdbc8fcca87307a.zip
Improve Eshell's behavior when waiting for processes
This has a few benefits. First, it fixes a race condition when killing old processes in 'eshell-command'. Second, the "wait" built-in command is now more useful. Finally, killing processes when exiting Eshell (via 'eshell-round-robin-kill') should be much faster. * lisp/eshell/esh-proc.el (esh-opt): Require. (eshell-wait-for-process): Make obsolete in favor of... (eshell-wait-for-processes): ... this. Accept a timeout and support PIDs. Update callers. (eshell/wait): New implementation accepting -t/--timeout. (eshell-round-robin-kill): Use 'eshell-wait-for-processes'. * lisp/eshell/eshell.el (eshell-command): Use 'eshell-round-robin-kill'. * doc/misc/eshell.texi (List of Built-ins): Document the new "wait" behavior. * etc/NEWS: Announce this change.
-rw-r--r--doc/misc/eshell.texi7
-rw-r--r--etc/NEWS6
-rw-r--r--lisp/eshell/esh-cmd.el2
-rw-r--r--lisp/eshell/esh-proc.el65
-rw-r--r--lisp/eshell/eshell.el7
5 files changed, 62 insertions, 25 deletions
diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi
index 69f94fab469..8547131194e 100644
--- a/doc/misc/eshell.texi
+++ b/doc/misc/eshell.texi
@@ -1201,8 +1201,11 @@ or a string, referring to an environment variable.
1201 1201
1202@cmindex wait 1202@cmindex wait
1203@cindex processes, waiting for 1203@cindex processes, waiting for
1204@item wait [@var{process}]@dots{} 1204@item wait [-t @var{timeout}] [@var{process}]@dots{}
1205Wait until each specified @var{process} has exited. 1205Wait until each specified @var{process} has exited. Processes can
1206either be process objects (@pxref{Processes, , , elisp, GNU Emacs Lisp
1207Reference Manual}) or integer PIDs. If you pass @code{-t} or
1208@code{--timeout}, wait at most that many seconds before exiting.
1206 1209
1207@cmindex which 1210@cmindex which
1208@item which @var{command}@dots{} 1211@item which @var{command}@dots{}
diff --git a/etc/NEWS b/etc/NEWS
index 8c49d4b24f6..f10f9ae4d65 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -54,6 +54,12 @@ this will prompt for confirmation before creating a new buffer when
54necessary. To restore the previous behavior, set this option to 54necessary. To restore the previous behavior, set this option to
55'confirm-kill-process'. 55'confirm-kill-process'.
56 56
57+++
58*** Eshell's built-in "wait" command now accepts a timeout.
59By passing "-t" or "--timeout", you can specify a maximum time to wait
60for the processes to exit. Additionally, you can now wait for external
61processes by passing their PIDs.
62
57** SHR 63** SHR
58 64
59+++ 65+++
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index 0b3137127d2..b936f68a57a 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -1299,7 +1299,7 @@ have been replaced by constants."
1299 (if-let (((memq (car form) eshell-deferrable-commands)) 1299 (if-let (((memq (car form) eshell-deferrable-commands))
1300 (procs (eshell-make-process-list result))) 1300 (procs (eshell-make-process-list result)))
1301 (if synchronous-p 1301 (if synchronous-p
1302 (apply #'eshell/wait procs) 1302 (funcall #'eshell-wait-for-processes procs)
1303 (eshell-manipulate form "inserting ignore form" 1303 (eshell-manipulate form "inserting ignore form"
1304 (setcar form 'ignore) 1304 (setcar form 'ignore)
1305 (setcdr form nil)) 1305 (setcdr form nil))
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index f982e2101f5..0dcdf3bb76c 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -25,6 +25,7 @@
25 25
26(require 'esh-arg) 26(require 'esh-arg)
27(require 'esh-io) 27(require 'esh-io)
28(require 'esh-opt)
28(require 'esh-util) 29(require 'esh-util)
29 30
30(require 'pcomplete) 31(require 'pcomplete)
@@ -184,16 +185,46 @@ This is like `process-live-p', but additionally checks whether
184 ;; cleared out the handles (see `eshell-sentinel'). 185 ;; cleared out the handles (see `eshell-sentinel').
185 (process-get process :eshell-handles))) 186 (process-get process :eshell-handles)))
186 187
187(defun eshell-wait-for-process (&rest procs) 188(defun eshell-wait-for-processes (&optional procs timeout)
188 "Wait until PROCS have successfully completed." 189 "Wait until PROCS have completed execution.
189 (dolist (proc procs) 190If TIMEOUT is non-nil, wait at most that many seconds. Return non-nil
190 (when (eshell-processp proc) 191if all the processes finished executing before the timeout expired."
191 (while (eshell-process-active-p proc) 192 (let ((expiration (when timeout (time-add (current-time) timeout))))
192 (when (input-pending-p) 193 (catch 'timeout
193 (discard-input)) 194 (dolist (proc procs)
194 (sit-for eshell-process-wait-time))))) 195 (while (if (processp proc)
196 (eshell-process-active-p proc)
197 (process-attributes proc))
198 (when (input-pending-p)
199 (discard-input))
200 (when (and expiration
201 (not (time-less-p (current-time) expiration)))
202 (throw 'timeout nil))
203 (sit-for eshell-process-wait-time)))
204 t)))
195 205
196(defalias 'eshell/wait #'eshell-wait-for-process) 206(defun eshell-wait-for-process (&rest procs)
207 "Wait until PROCS have completed execution."
208 (declare (obsolete 'eshell-wait-for-processes "31.1"))
209 (eshell-wait-for-processes procs))
210
211(defun eshell/wait (&rest args)
212 "Wait until processes have completed execution."
213 (eshell-eval-using-options
214 "wait" args
215 '((?h "help" nil nil "show this usage screen")
216 (?t "timeout" t timeout "timeout in seconds")
217 :preserve-args
218 :show-usage
219 :usage "[OPTION] PROCESS...
220Wait until PROCESS(es) have completed execution.")
221 (when (stringp timeout)
222 (setq timeout (string-to-number timeout)))
223 (dolist (arg args)
224 (unless (or (processp arg) (natnump arg))
225 (error "wait: invalid argument type: %s" (type-of arg))))
226 (unless (eshell-wait-for-processes args timeout)
227 (error "wait: timed out after %s seconds" timeout))))
197 228
198(defun eshell/jobs () 229(defun eshell/jobs ()
199 "List processes, if there are any." 230 "List processes, if there are any."
@@ -626,16 +657,14 @@ long to delay between signals."
626(defun eshell-round-robin-kill (&optional query) 657(defun eshell-round-robin-kill (&optional query)
627 "Kill current process by trying various signals in sequence. 658 "Kill current process by trying various signals in sequence.
628See the variable `eshell-kill-processes-on-exit'." 659See the variable `eshell-kill-processes-on-exit'."
629 (let ((sigs eshell-kill-process-signals)) 660 (catch 'done
630 (while sigs 661 (dolist (sig eshell-kill-process-signals)
631 (eshell-process-interact 662 (eshell-process-interact
632 (lambda (proc) 663 (lambda (proc) (signal-process proc sig)) t query)
633 (signal-process (process-id proc) (car sigs))) t query) 664 (when (eshell-wait-for-processes (mapcar #'car eshell-process-list)
634 (setq query nil) 665 eshell-kill-process-wait-time)
635 (if (not eshell-process-list) 666 (throw 'done nil))
636 (setq sigs nil) 667 (setq query nil))))
637 (sleep-for eshell-kill-process-wait-time)
638 (setq sigs (cdr sigs))))))
639 668
640(defun eshell-query-kill-processes () 669(defun eshell-query-kill-processes ()
641 "Kill processes belonging to the current Eshell buffer, possibly with query." 670 "Kill processes belonging to the current Eshell buffer, possibly with query."
diff --git a/lisp/eshell/eshell.el b/lisp/eshell/eshell.el
index 568f6745067..b7be3dd1643 100644
--- a/lisp/eshell/eshell.el
+++ b/lisp/eshell/eshell.el
@@ -176,7 +176,7 @@
176 (require 'cl-lib)) 176 (require 'cl-lib))
177(require 'esh-util) 177(require 'esh-util)
178(require 'esh-module) ;For eshell-using-module 178(require 'esh-module) ;For eshell-using-module
179(require 'esh-proc) ;For eshell-wait-for-process 179(require 'esh-proc) ;For eshell-wait-for-processes
180(require 'esh-io) ;For eshell-last-command-status 180(require 'esh-io) ;For eshell-last-command-status
181(require 'esh-cmd) 181(require 'esh-cmd)
182 182
@@ -357,8 +357,7 @@ buffer is already taken by another running shell command."
357 (with-current-buffer bufname 357 (with-current-buffer bufname
358 ;; Stop all the processes in the old buffer (there may 358 ;; Stop all the processes in the old buffer (there may
359 ;; be several). 359 ;; be several).
360 (eshell-process-interact #'interrupt-process t)) 360 (eshell-round-robin-kill))
361 (accept-process-output)
362 (kill-buffer bufname)) 361 (kill-buffer bufname))
363 ((eq eshell-command-async-buffer 'confirm-new-buffer) 362 ((eq eshell-command-async-buffer 'confirm-new-buffer)
364 (shell-command--same-buffer-confirm "Use a new buffer") 363 (shell-command--same-buffer-confirm "Use a new buffer")
@@ -377,7 +376,7 @@ buffer is already taken by another running shell command."
377 ;; make the output as attractive as possible, with no 376 ;; make the output as attractive as possible, with no
378 ;; extraneous newlines 377 ;; extraneous newlines
379 (unless async 378 (unless async
380 (apply #'eshell-wait-for-process (cadr eshell-foreground-command)) 379 (funcall #'eshell-wait-for-processes (cadr eshell-foreground-command))
381 (cl-assert (not eshell-foreground-command)) 380 (cl-assert (not eshell-foreground-command))
382 (goto-char (point-max)) 381 (goto-char (point-max))
383 (while (and (bolp) (not (bobp))) 382 (while (and (bolp) (not (bobp)))