GNU bug report logs - #60088
29.0.60; Eglot loses messages from gopls

Previous Next

Package: emacs;

Reported by: João Távora <joaotavora <at> gmail.com>

Date: Thu, 15 Dec 2022 11:55:02 UTC

Severity: normal

Found in version 29.0.60

Done: Stefan Kangas <stefankangas <at> gmail.com>

Bug is archived. No further changes may be made.

To add a comment to this bug, you must first unarchive it, by sending
a message to control AT debbugs.gnu.org, with unarchive 60088 in the body.
You can then email your comments to 60088 AT debbugs.gnu.org in the normal way.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to bug-gnu-emacs <at> gnu.org:
bug#60088; Package emacs. (Thu, 15 Dec 2022 11:55:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to João Távora <joaotavora <at> gmail.com>:
New bug report received and forwarded. Copy sent to bug-gnu-emacs <at> gnu.org. (Thu, 15 Dec 2022 11:55:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: João Távora <joaotavora <at> gmail.com>
To: bug-gnu-emacs <at> gnu.org, eliz <at> gnu.org
Subject: 29.0.60; Eglot loses messages from gopls
Date: Thu, 15 Dec 2022 11:56:15 +0000
[Eli, I have CC'ed you directly because this bug concerns a technique to
avoid recursive process filters, which seems like an area you are expert
in.]

Hi, 

In some situations, Eglot is losing some messages from the Go language
server, 'gopls'.  The problem has been reproduced and identified.

For context, this started in:

* https://github.com/joaotavora/eglot/issues/587#issuecomment-1221072833
* https://github.com/golang/go/issues/54559

(In the former link, there are reports of what seem to be other
problems.  The reports are missing clear reproduction recipes, so they
may not be the same problem I'm about to describe.)

Reproduction:

  Ensure go-mode is installed from https://melpa.org/#/go-mode
  $ go install golang.org/x/tools/gopls <at> latest
  $ git clone git clone https://github.com/google/nftables
  $ emacs -Q -f package-initialize nftables/nftables_test.go
  M-x eglot

The last step will connect to the server, exchange some messages.  It
will not block Emacs, but the Eglot session will be completely
non-functional.

Analysis:

Many essential JSONRPC messages have not been exchanged, they have been
lost.  The bytes of the lost messages from the gopls side can be seen in
the process buffer (M-x list-processes, then click the link to the
hidden process buffer).

The problem happens in jsonrpc.el's jsonrpc--process-filter.  The code
does not expect this function to be called recursively, but that can
happen if the client code invoked by its jsonrpc--connection-receive
callee eventually sends a follow-up message to the server.  This is a
common scenario, especially during LSP initialiation.  It has been
working fine for a number of years and a large number of servers.

However:

* if that follow-up message is large enough (this is why the largish
  nftables_test.go file is needed) AND

* the server has already sent us some output with just the right (wrong)
  timing.

then the process-send-string call that is eventually reached will in
fact cause more output to arrive from the process and a recursive
invocation of the process filter.

Resolution:

I've looked at a number of ways to solve this problem, from avoiding the
follow up message in Eglot's side to different techniques on the jsonrpc
side.  None of them are as satisfactory as the patch after my sig, which
simply re-schedules the would-be recursive call to re-run from within a
timer stack as soon as possible.

The only problem I can envision with this approach would be if multiple
recursive calls are launched from the same jsonrpc--connection-receive
stack frame and would somehow not be executed in the correct order.
That doesn't seem to be a problem after some experiments like

(progn
  (run-at-time 0 nil #'insert "a")
  (run-at-time 0 nil #'insert "b")
  (run-at-time 0 nil #'insert "c"))

which inserts the predicatbly sane "abc".  If this in-order execution is
somehow not guaranteed, there is the alternate more complex technique of
inserting the output into the process buffer immediately, and schedule a
single processing call thereafter.

I will install the following patch soon unless someone objects to this
approach.

João

diff --git a/lisp/jsonrpc.el b/lisp/jsonrpc.el
index 90833e1c1d..76bbf28138 100644
--- a/lisp/jsonrpc.el
+++ b/lisp/jsonrpc.el
@@ -548,11 +548,27 @@ jsonrpc--process-sentinel
         (delete-process proc)
         (funcall (jsonrpc--on-shutdown connection) connection)))))
 
-(defun jsonrpc--process-filter (proc string)
+(defvar jsonrpc--in-process-filter nil
+  "Non-nil if inside `jsonrpc--process-filter'.")
+
+(cl-defun jsonrpc--process-filter (proc string)
   "Called when new data STRING has arrived for PROC."
+  (when jsonrpc--in-process-filter
+    ;; Problematic recursive process filters may happen if
+    ;; `jsonrpc--connection-receive', called by us, eventually calls
+    ;; client code which calls `process-send-string' (which see) to,
+    ;; say send a follow-up message.  If that happens to writes enough
+    ;; bytes for additional output to be received, we have a problem.
+    ;;
+    ;; In that case, remove recursiveness by re-scheduling ourselves to
+    ;; run from within a timer as soon as possible.
+
+    (run-at-time 0 nil #'jsonrpc--process-filter proc string)
+    (cl-return-from jsonrpc--process-filter))
   (when (buffer-live-p (process-buffer proc))
     (with-current-buffer (process-buffer proc)
       (let* ((inhibit-read-only t)
+             (jsonrpc--in-process-filter t)
              (connection (process-get proc 'jsonrpc-connection))
              (expected-bytes (jsonrpc--expected-bytes connection)))
         ;; Insert the text, advancing the process marker.













Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#60088; Package emacs. (Fri, 16 Dec 2022 08:56:01 GMT) Full text and rfc822 format available.

Message #8 received at 60088 <at> debbugs.gnu.org (full text, mbox):

From: João Távora <joaotavora <at> gmail.com>
To: 60088 <at> debbugs.gnu.org, eliz <at> gnu.org
Subject: Re: bug#60088: 29.0.60; Eglot loses messages from gopls
Date: Fri, 16 Dec 2022 08:56:56 +0000
[Message part 1 (text/plain, inline)]
I've now pushed the patch/bugfix to emacs-29 and bumped
version of jsonrpc.el so we should get a new GNU ELPA package
soon.

GNU ELPA Eglot users should wait for the package to be bumped
too.

João

On Thu, Dec 15, 2022 at 11:55 AM João Távora <joaotavora <at> gmail.com> wrote:

> [Eli, I have CC'ed you directly because this bug concerns a technique to
> avoid recursive process filters, which seems like an area you are expert
> in.]
>
> Hi,
>
> In some situations, Eglot is losing some messages from the Go language
> server, 'gopls'.  The problem has been reproduced and identified.
>
> For context, this started in:
>
> * https://github.com/joaotavora/eglot/issues/587#issuecomment-1221072833
> * https://github.com/golang/go/issues/54559
>
> (In the former link, there are reports of what seem to be other
> problems.  The reports are missing clear reproduction recipes, so they
> may not be the same problem I'm about to describe.)
>
> Reproduction:
>
>   Ensure go-mode is installed from https://melpa.org/#/go-mode
>   $ go install golang.org/x/tools/gopls <at> latest
>   $ git clone git clone https://github.com/google/nftables
>   $ emacs -Q -f package-initialize nftables/nftables_test.go
>   M-x eglot
>
> The last step will connect to the server, exchange some messages.  It
> will not block Emacs, but the Eglot session will be completely
> non-functional.
>
> Analysis:
>
> Many essential JSONRPC messages have not been exchanged, they have been
> lost.  The bytes of the lost messages from the gopls side can be seen in
> the process buffer (M-x list-processes, then click the link to the
> hidden process buffer).
>
> The problem happens in jsonrpc.el's jsonrpc--process-filter.  The code
> does not expect this function to be called recursively, but that can
> happen if the client code invoked by its jsonrpc--connection-receive
> callee eventually sends a follow-up message to the server.  This is a
> common scenario, especially during LSP initialiation.  It has been
> working fine for a number of years and a large number of servers.
>
> However:
>
> * if that follow-up message is large enough (this is why the largish
>   nftables_test.go file is needed) AND
>
> * the server has already sent us some output with just the right (wrong)
>   timing.
>
> then the process-send-string call that is eventually reached will in
> fact cause more output to arrive from the process and a recursive
> invocation of the process filter.
>
> Resolution:
>
> I've looked at a number of ways to solve this problem, from avoiding the
> follow up message in Eglot's side to different techniques on the jsonrpc
> side.  None of them are as satisfactory as the patch after my sig, which
> simply re-schedules the would-be recursive call to re-run from within a
> timer stack as soon as possible.
>
> The only problem I can envision with this approach would be if multiple
> recursive calls are launched from the same jsonrpc--connection-receive
> stack frame and would somehow not be executed in the correct order.
> That doesn't seem to be a problem after some experiments like
>
> (progn
>   (run-at-time 0 nil #'insert "a")
>   (run-at-time 0 nil #'insert "b")
>   (run-at-time 0 nil #'insert "c"))
>
> which inserts the predicatbly sane "abc".  If this in-order execution is
> somehow not guaranteed, there is the alternate more complex technique of
> inserting the output into the process buffer immediately, and schedule a
> single processing call thereafter.
>
> I will install the following patch soon unless someone objects to this
> approach.
>
> João
>
> diff --git a/lisp/jsonrpc.el b/lisp/jsonrpc.el
> index 90833e1c1d..76bbf28138 100644
> --- a/lisp/jsonrpc.el
> +++ b/lisp/jsonrpc.el
> @@ -548,11 +548,27 @@ jsonrpc--process-sentinel
>          (delete-process proc)
>          (funcall (jsonrpc--on-shutdown connection) connection)))))
>
> -(defun jsonrpc--process-filter (proc string)
> +(defvar jsonrpc--in-process-filter nil
> +  "Non-nil if inside `jsonrpc--process-filter'.")
> +
> +(cl-defun jsonrpc--process-filter (proc string)
>    "Called when new data STRING has arrived for PROC."
> +  (when jsonrpc--in-process-filter
> +    ;; Problematic recursive process filters may happen if
> +    ;; `jsonrpc--connection-receive', called by us, eventually calls
> +    ;; client code which calls `process-send-string' (which see) to,
> +    ;; say send a follow-up message.  If that happens to writes enough
> +    ;; bytes for additional output to be received, we have a problem.
> +    ;;
> +    ;; In that case, remove recursiveness by re-scheduling ourselves to
> +    ;; run from within a timer as soon as possible.
> +
> +    (run-at-time 0 nil #'jsonrpc--process-filter proc string)
> +    (cl-return-from jsonrpc--process-filter))
>    (when (buffer-live-p (process-buffer proc))
>      (with-current-buffer (process-buffer proc)
>        (let* ((inhibit-read-only t)
> +             (jsonrpc--in-process-filter t)
>               (connection (process-get proc 'jsonrpc-connection))
>               (expected-bytes (jsonrpc--expected-bytes connection)))
>          ;; Insert the text, advancing the process marker.
>
>
>
>
>
>
>
>
>
>
>
>
>

-- 
João Távora
[Message part 2 (text/html, inline)]

Reply sent to Stefan Kangas <stefankangas <at> gmail.com>:
You have taken responsibility. (Tue, 05 Sep 2023 23:52:01 GMT) Full text and rfc822 format available.

Notification sent to João Távora <joaotavora <at> gmail.com>:
bug acknowledged by developer. (Tue, 05 Sep 2023 23:52:02 GMT) Full text and rfc822 format available.

Message #13 received at 60088-done <at> debbugs.gnu.org (full text, mbox):

From: Stefan Kangas <stefankangas <at> gmail.com>
To: João Távora <joaotavora <at> gmail.com>
Cc: eliz <at> gnu.org, 60088-done <at> debbugs.gnu.org
Subject: Re: bug#60088: 29.0.60; Eglot loses messages from gopls
Date: Tue, 5 Sep 2023 16:51:34 -0700
João Távora <joaotavora <at> gmail.com> writes:

> I've now pushed the patch/bugfix to emacs-29 and bumped
> version of jsonrpc.el so we should get a new GNU ELPA package
> soon.

I'm therefore closing this bug report.




bug archived. Request was from Debbugs Internal Request <help-debbugs <at> gnu.org> to internal_control <at> debbugs.gnu.org. (Wed, 04 Oct 2023 11:24:43 GMT) Full text and rfc822 format available.

This bug report was last modified 202 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.