16

(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?

Trey
  • 865
  • 5
  • 20
  • Is there a way to reproduce these type of issues programmatically so the community can troubleshoot? – Melioratus Apr 02 '19 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 02 '19 at 16:59
  • 1
    This is a frequently-mentioned case here, for instance: https://emacs.stackexchange.com/questions/8147/using-desktop-mode-with-emacs-daemon or https://emacs.stackexchange.com/questions/31621/handle-stale-desktop-lock-files-after-emacs-system-crash may provide context. – Trey Apr 02 '19 at 17:01
  • This bug seems related: [Bug#13697 - A way to tell if Emacs can interact with the user](https://debbugs.gnu.org/cgi/bugreport.cgi?bug=13697), but nobody has worked on it, as far as I know. – npostavs Apr 03 '19 at 01: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 03 '19 at 21:51
  • @npostavs Thanks for the response on the bug. The site help said that bugs were archived after being closed 1 month and to reopen them you'd have to unarchive them—I didn't understand that these were independent things, so I was confused. – Trey Apr 03 '19 at 23:20

3 Answers3

3

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.

Tobias
  • 32,569
  • 1
  • 34
  • 75
  • @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 03 '19 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 03 '19 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 03 '19 at 21:14
  • @Trey Could you join in a [chat](https://chat.stackexchange.com/rooms/91942/emacsclient-daemonroom)? – Tobias Apr 03 '19 at 21:38
2

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.

Tobias
  • 32,569
  • 1
  • 34
  • 75
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.

Stefan
  • 26,154
  • 3
  • 46
  • 84
  • 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 02 '19 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 02 '19 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 02 '19 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 02 '19 at 17:15
  • In either case, though, most users don't peruse `*Messages*`—and while the little-used [`*Warnings*`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Warning-Basics.html#Warning-Basics) 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 02 '19 at 17:16
  • Stefan—from perusal of the docs and Elisp, I agree your solution seems plausible. But my Elisp is poor (or rather, my ability to use Emacs programmability is poor—my Elisp as a language is fine). If you have the knowledge to add the necessary—hooks? Advice?—here I reach the limits of my understanding of Emacs under the hood, please supply them—and the bounty is yours ;-) – Trey Apr 02 '19 at 17:21
  • Oh—also note that `yes-or-no-p`, alone, is not sufficient—note my example asking about restoring a TRAMP buffer asking for a password. Recently, I've had to kill hung daemons waiting for GPG passphrases and Git SSH key token passphrases, too, then excise the code that was allowing this to happen at startup. Using `use-package`'s helpful `:defer` flag can fix these when identified—but it also negates the reason to have an autostarting daemon: an Emacs that doesn't have to load stuff. It would be nice to say "defer _only_ on attempt at interaction"—though the plumbing would be difficult since... – Trey Apr 02 '19 at 17:56
  • ... doing this the way _I_ can see to do it would result in three problems: 1) a maximum of one thing could be waiting on minibuffer input; any other attempts wouldn't be queued, they'd just error out; and 2) similarly, anything that tried to write directly to minibuffer rather than just messages would also defer/err, and 3) as you know if you've ever had something early in startup prompt and something later write to the minibuffer before you've answered, the question changes to just `Please answer y(es) or n(o)` — so, if deferred, you have absolutely no idea what you're saying yes or no _to!_ – Trey Apr 02 '19 at 18:00
  • @Trey: I don't understand, you seem to be arguing against fixing the problem. In any case, this is not the place to discuss it. Please `M-x report-emacs-bug` instead. – Stefan Apr 02 '19 at 18:54
  • What? No, I'm talking about the issues. I asked the question because I know Emacs is extensible enough that all these issues _might_ be solvable. If you know Emacs well enough to say, no, they're not, then yes, I'll submit a bug. But what part of this is arguing against fixing? If, upon first connecting with a client after interactions blocked, you were led through answering each of the queued interactions, that would be a 100% solution, not an argument against solving it. – Trey Apr 02 '19 at 19:00