1

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:

  1. top shows that the server-eval-at emacs (client) is consuming 100% CPU.
  2. strace shows that the server-eval-at emacs (client) is calling read on stdin (fd 0) repeatedly, and receiving an empty string each time
  3. strace shows that the eval emacs calls read on stdin twice: once to read the command, and once receiving an empty string before exiting.
  4. gdb on the server-eval-at emacs (client) has a Lisp Backtrace with read-string at 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.

4
  • Not sure what "but the process does not exit!" means. It seems work as expected here with Emacs 27.1 on macOS. Your code won't quit properly as read-string always returns a string (non-nil), and I don't see why you wrap it with ignore-errors, the function is not expected to fail. Emacs quits while it encounters an error, such as "End of file during parsing:" from read when you does not enter a valid sexp.
    – xuchunyang
    Nov 11, 2020 at 2:30
  • read-string signals an error at the end of input, this is easy to verify. ignore-errors ignores that error and returns nil which 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 using server-eval-at. If what you are saying is true, then not using server-eval-at actually causes emacs to behave incorrectly, but I don't think that's the case.
    – Chris Hunt
    Nov 11, 2020 at 2:43
  • 1
    You're correct about read-string. I didn't realize I can use C-d, normally user can quit the minibuffer via C-g which not work in batch mode. I didn't test your exact code, I simply test server-eval-at. Your program does not kill the first Emacs process, it remains running after the program ends.
    – xuchunyang
    Nov 11, 2020 at 2:59
  • Also posted to help-gnu-emacs
    – Chris Hunt
    Nov 13, 2020 at 1:10

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.