I am trying to create a script that, when run with emacs, will read commands from stdin a line at a time, execute them in another emacs process (with server-eval-at) and print the result. The process should exit when no more input is available. I have come up with a basic protocol and everything seems to work when using eval locally, but after introducing server-eval-at the emacs process hangs instead of exiting at the end of input.
For initial development I was doing the evaluation inline, in the same process that was reading the input. That seems to work well. That script looks like
(while (setq command (ignore-errors (read-string "")))
(setq command (read command))
(message "[DEBUG] Received command %s" command)
(let ((result (eval command)))
(message "[DEBUG] Response received %s" result)))
In a shell script, for easier testing:
#!/bin/sh
cd "$(mktemp -d)"
cat <<EOF > script.el
(while (setq command (ignore-errors (read-string "")))
(setq command (read command))
(message "[DEBUG] Received command %s" command)
(let ((result (eval command)))
(message "[DEBUG] Response received %s" result)))
EOF
echo '(message "hello world")' |
emacs --quick --batch --script "$PWD/script.el"
An example of running this, with output:
$ ./t2
[DEBUG] Received command (message hello world)
hello world
[DEBUG] Response received hello world
$
As you can see, the debug messages are printed. The process exits at the end of input. All of this is expected and OK.
Now I want to run the incoming commands on the server, so I use server-eval-at. Here is that script
(require 'server)
(while (setq command (ignore-errors (read-string "")))
(setq command (read command))
(message "[DEBUG] Received command %s" command)
(let ((result (server-eval-at (getenv "RPC_SERVER_NAME") command)))
(message "[DEBUG] Response received %s" result)))
Another shell script, for easier testing
#!/bin/sh
cd "$(mktemp -d)"
export RPC_SERVER_NAME="$PWD/emacs"
emacs --quick "--fg-daemon=$RPC_SERVER_NAME" >stdout.log 2>stderr.log &
sleep 1
cat <<EOF > script.el
(require 'server)
(while (setq command (ignore-errors (read-string "")))
(setq command (read command))
(message "[DEBUG] Received command %s" command)
(let ((result (server-eval-at (getenv "RPC_SERVER_NAME") command)))
(message "[DEBUG] Response received %s" result)))
EOF
echo '(message "hello world")' |
emacs --quick --batch --script "$PWD/script.el"
An example of running this, with output:
$ ./t1
[DEBUG] Received command (message hello world)
[DEBUG] Response received hello world
The output looks fine, but the process does not exit!
I have introspected these processes a few ways:
- top shows that the
server-eval-atemacs (client) is consuming 100% CPU. - strace shows that the
server-eval-atemacs (client) is callingreadon stdin (fd 0) repeatedly, and receiving an empty string each time - strace shows that the
evalemacs callsreadon stdin twice: once to read the command, and once receiving an empty string before exiting. - gdb on the
server-eval-atemacs (client) has a Lisp Backtrace withread-stringat the top.
I inlined server-eval-at and was able to identify that calling accept-process-output
is necessary for the subsequent call to read-string (in the next loop iteration) to behave differently, but it is hard for me to
dig much deeper than that.
I tried these same steps against emacs 26.3 and observed the same result.
I am using emacs 27.1 on Ubuntu Linux 18.04, as installed with evm.
read-stringalways returns a string (non-nil), and I don't see why you wrap it withignore-errors, the function is not expected to fail. Emacs quits while it encounters an error, such as "End of file during parsing:" fromreadwhen you does not enter a valid sexp. – xuchunyang Nov 11 '20 at 02:30read-stringsignals an error at the end of input, this is easy to verify.ignore-errorsignores that error and returnsnilwhich will cleanly break out of the loop. After the loop emacs will exit because it is the behavior when running with--script/--batch. As I mentioned above, all of this behaves as I expect when not usingserver-eval-at. If what you are saying is true, then not usingserver-eval-atactually causes emacs to behave incorrectly, but I don't think that's the case. – Chris Hunt Nov 11 '20 at 02:43read-string. I didn't realize I can use C-d, normally user can quit the minibuffer viaC-gwhich not work in batch mode. I didn't test your exact code, I simply testserver-eval-at. Your program does not kill the first Emacs process, it remains running after the program ends. – xuchunyang Nov 11 '20 at 02:59