15

(Note that, title to the contrary, this question is not the same as How to start in daemon mode and suppress interactive dialogs?, as that question was "answered" by the submitter eliminating what was causing a particular prompt to appear.)

I'd like to know if there is a general way to keep emacs --daemon from hanging forever waiting for an answer to a prompt displayed in a minibuffer that doesn't exist yet.

It's impossible to connect with an emacsclient to answer these prompts, because the server doesn't start until Emacs finishes the startup sequence. (This means, if you have ALTERNATE_EDITOR set to the empty string, which makes an emacsclient that can't find a server start a new daemon, you can end up with multiple Emacs daemons all stuck and waiting.) I have to killall emacs and fix the problem before continuing.

I can play whack-a-mole with each thing causing a prompt on startup when I identify it (by starting Emacs in non-daemon mode and seeing what it's asking), but it's not a solution because it can't stop the next daemon from hanging on startup for a new reason.

To give an example: a common reason it would hang was after a system reboot or Emacs crash, when the first post-reboot Emacs wanted to know whether it was okay to steal lockfiles from the defunct Emacs. I could fix that by creating advice to make that prompt always answer "yes" without interaction. But then, one of the files that was open at the previous session save was a TRAMP file requiring a sudo or SSH password, so the daemon's stuck waiting on a password prompt. So I fix that by manually editing the session file (with vi or emacs -q!) to remove the offending files—but that doesn't keep it from happening next time.

So, I can stop loading my session automatically on startup and change it to a command I must manually execute from my first emacsclient. But if it isn't loading my session in the background so it's ready by the time I'm ready to use it, the entire purpose of the daemon is lost!

So what I'd like is:

  • (Best) Some way to defer minibuffer prompts until I open an emacsclient, while still completing the rest of initialization.
  • (OK) Some way to make all minibuffer prompts I haven't already advised otherwise as described above just return no unless an emacsclient is running. I can live with my TRAMP buffers erroring out so long as it mostly works.

Is there any way to achieve either of these goals?

  • Is there a way to reproduce these type of issues programmatically so the community can troubleshoot? – Melioratus Apr 2 at 14:59
  • 1
    Well, as I wrote in the first line, it's quite easy to fix a given example... "Doctor, it hurts when I do this..." "Then don't do that." The issue is the general case. But a simple way to create the issue is to have startup restore desktop through (read-desktop), then, before running emacs --daemon, create a bogus lock file by putting an integer into .emacs.desktop.lock (where to put that file, unfortunately, depends on your configuration, but probably either your homedir or ~/.emacs.d/. – Trey Apr 2 at 16:59
  • 1
    This is a frequently-mentioned case here, for instance: emacs.stackexchange.com/questions/8147/… or emacs.stackexchange.com/questions/31621/… may provide context. – Trey Apr 2 at 17:01
  • This bug seems related: Bug#13697 - A way to tell if Emacs can interact with the user, but nobody has worked on it, as far as I know. – npostavs Apr 3 at 1:09
  • @npostavs Thanks for the link—I've annotated the bug, though it took a false start that I commented on here (since deleted) before I figured it out! – Trey Apr 3 at 21:51
1
+50

Our discussion cleared that you do not have any X-server running this renders my first solution useless for you.

In the following I present a second solution that works with text terminal frames.

When your initialization requires user input through one the functions advised with avoid-initial-terminal Emacs waits until you open a text terminal frame. The prompt appears in the minibuffer of that frame and you can give your interactive response.

Code-related infos are given as comments in the code. There are TODO markers with descriptions that show you where to insert your own configuration. Currently there are test forms in there that validate the code.

;; TODO: Do here configure the server if needed.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Startup the server:
;; Analysis of read_from_minibuffer in src/minibuf.c and daemon_type in src/emacs.c
;; shows that daemon-initialized must have run before read-passwd / read-string
;; works on frames. Before it only works on stdin & stdout.
(server-start) ;;< early start
(let ((after-init-time before-init-time))
  (daemon-initialized)) ;; Finalize the daemon, 

(advice-add 'daemon-initialized :override #'ignore)
;;< Ignore `daemon-initialized' after initialization. It may only run once!
;; Now the background emacs is no longer marked as daemon. It just runs the server.

(defun prevent-server-start (&rest _ignore)
  "Prevent starting a server one time after `server-start' has been advised with this."
  (advice-remove 'server-start #'prevent-server-start))

(advice-add 'server-start :override #'prevent-server-start)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Prepare waiting for a real terminal frame when user input is required:

(defun avoid-initial-terminal (fun &rest args)
  "Wait until we are no longer on \"intial-terminal\".
Afterwards run `fun' with frame on the other terminal selected."
  (message "Avoiding initial terminal. Terminal: %S" (get-device-terminal nil))
  (while (string-equal
      (terminal-name (get-device-terminal nil))
      "initial_terminal")
    (sleep-for 1))
  ;; (message "Selected frame: %S; Running %S with %S." (selected-frame) fun args)
  (apply fun args))

(advice-add 'read-string :around #'avoid-initial-terminal)

(advice-add 'read-passwd :around #'avoid-initial-terminal)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO: Your initialization that is not daemon related
;; and may require user input:

;; Currently this is just a test.
(read-passwd "Passwd: ")

(read-string "String: ")

(y-or-n-p "y-or-n query")

Test: Emacs-version: 26.1

1st) Run emacs --daemon on a console.

2nd) Run emacsclient --tty on another console. You are asked there for a password and a string. Afterwards you are also required to answer a y-or-n-p query.

2

Not exactly what you are asking for but maybe a solution to your original problem:

I'd like to know if there is a general way to keep emacs --daemon from hanging forever waiting for an answer to a prompt displayed in a minibuffer that doesn't exist yet.

If the daemon gives you a graphical frame for answering questions arising in its startup-phase you do not get stuck anymore.

The code below defines a general advice my-with-initial-frame that opens a frame on the first available display (e.g., :0.0).

That advice can be added easily to querying commands like y-or-n-p or read-passwd, as it is demonstrated below.

Just opening a frame gives you a rather coarse possibility to answer the queries on the user interface. One could also use a dialog box for y-or-n-p but that would require special solutions for specific querying commands. I wanted to avoid that.

If you try that code in your init file make sure it is the first thing there.

(when (daemonp)

  (defun my-with-initial-frame (&rest _)
    "Ensure a frame on display :0.0 and ignore args."
    (let* ((display-list (x-display-list))
           (display-re (and display-list (regexp-opt display-list)))
           (term (and display-re (cl-some (lambda (term) (and (string-match display-re (terminal-name term)) term)) (terminal-list))))
           (frame (and term (cl-some (lambda (frame) (and (frame-live-p frame) frame)) (frames-on-display-list term)))))
      (select-frame (or frame (make-frame-on-display (getenv "DISPLAY"))))))

  (message "Advising querying functions with `my-with-initial-frame'.")
  (advice-add 'y-or-n-p :before #'my-with-initial-frame)
  (advice-add 'read-passwd :before #'my-with-initial-frame))

Test:

Assumptions:

Have a running xserver which programs can connect to via the DISPLAY environment variable.

Input at xterm:

emacs --daemon

emacsclient --eval '(y-or-n-p "A")'

There opens a frame with the y-or-n-p query prompt A (y or n). Answer that query and try again:

emacsclient --eval '(y-or-n-p "B")'

New query with prompt B (y or n) in the same frame. Close that frame, e.g., with C-x 5 0 and try again:

emacsclient --eval '(y-or-n-p "C")'

A new frame opens with the query prompt C (y or n).

The same works for password input.

  • @Trey I had some problem with my code (actually with the testing). Starting up the x-server did not work the first time. I did not notice initially since I did not re-start the daemon. I corrected that now. Please test again. Thanks. – Tobias Apr 3 at 18:05
  • My Linux virtual doesn't have a graphical terminal attached, so I can't run xterm. Also, I don't think this solution can work for those who do—if you have the daemon set up to run at startup, it would try to open a frame on top of the login screen, which isn't allowed, so it crashes. – Trey Apr 3 at 20:01
  • Tobias, apologies if the above sounded brusque— I replied on my phone and may have cut myself short, so let me try to elaborate: the only advantage I can see of what you describe over not using the daemon and running server-start at the end of startup instead is that, if you happen to have a clean startup, you won't have to wait. But... you will have to wait, because unless I misunderstand, you can't put the task to start the Emacs daemon into your system login script since a GUI won't be available then. (And in a case like mine, it never will be later, either.) – Trey Apr 3 at 21:14
  • @Trey Could you join in a chat? – Tobias Apr 3 at 21:38
1

I think defering the prompts is going to be difficult in general, but it should be fairly easy to change Emacs so that such prompts immediately signal an error.

Not only that, but if you can't answer those prompts without a lot of gymnastics, I think it qualifies as a bug, so I'd recommend you submit a bug report for that.

  • I think I need a bit more detail. Consider the desktop save-file lock I mention in the comment to the bounty above. How would one go about changing the Warning: desktop file appears to be in use by PID xxx. Using it may cause conflicts. Use it anyway? (y or n) prompt into an error, without specifically referring to "desktop" in some way (because that way, being nongeneral, lies whack-a-mole)? – Trey Apr 2 at 17:05
  • Stefan, also there's an issue that doesn't bother me because I don't run the Emacs daemon this way, but to make a generally useful answer might need addressing: erroring out fatally will cause Emacs started up via systemd or other watchdogs to restart in a loop. But ignoring errors and just logging to *Messages* is probably insufficient heads-up on first client connect that something may be seriously awry and need immediate attention before the user attempts any stateful operations. – Trey Apr 2 at 17:08
  • (To clarify for those who don't use the daemon—if you start it manually, either via emacs --daemon or by starting emacsclient with the ALTERNATE_EDITOR environment variable set to the empty string, you will see the output that ordinarily goes to *Messages* echoed in the terminal until the daemon completes initialization and Emacs is ready. But many have Emacs start the daemon at system startup or login time, and the output is either logged or thrown away. – Trey Apr 2 at 17:11
  • 1
    @Trey: the error signaling shouldn't be in desktop but in the y-or-n-p function (or lower yet). We have some mechanism to delay displaying errors that happened during startup, so we could use that to display them when the first emacsclient connects to the daemon. – Stefan Apr 2 at 17:15
  • In either case, though, most users don't peruse *Messages*—and while the little-used *Warnings* system does pop up a window to the buffer if an active frame exists when the warning is generated, in this case, no frame exists, and it doesn't appear easy to defer the pop-up until the first emacsclient following the warning's issue. If that could be done, your suggestion of making a pre-client yes-or-no-p warn instead would be quite ideal. (I doubt users comb *Messages* on startup!) – Trey Apr 2 at 17:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.