Subject: Re: bug#62194: 30.0.50; Two Eglot-over-Tramp tests are failing on master, passing on emacs-29

From: [email-masked]
Date: Wed, 15 Mar 2023 21:36:05 +0100

Michael Albinus <[email-masked]> writes:

> That is, file "project/merdix.c" is viewed, which has the absolute path
> "/ssh:albinus@detlef:/tmp/eglot--fixture2kRNPt/project/merdix.c". It has
> been loaded into the buffer successfully, and 'run-hooks(find-file-hook)'
> is applied. Since the file is under vc control, 'vc-registerd' is invoked.
> Some lines up in the backtrace, we see
>
> --8<---------------cut here---------------start------------->8---
>   jsonrpc--process-filter(#<process EGLOT (project/(c-mode c-ts-mode c
>   accept-process-output(#<process EGLOT (project/(c-mode c-ts-mode c++
>   tramp-accept-process-output(#<process *tramp/ssh albinus@detlef*>)
>   tramp-wait-for-regexp(#<process *tramp/ssh albinus@detlef*> nil "\\(
>   tramp-wait-for-output(#<process *tramp/ssh albinus@detlef*>)
>   tramp-send-command((tramp-file-name "ssh" "albinus" nil "detlef" nil
>   tramp-send-command-and-check((tramp-file-name "ssh" "albinus" nil "d
>   tramp-run-test("-d" "/ssh:albinus@detlef:/tmp/eglot--fixture2kRNPt")
>   tramp-sh-handle-file-directory-p("/ssh:albinus@detlef:/tmp/eglot--fi
> --8<---------------cut here---------------end--------------->8---
>
> Tramp needs to know, whether a remote file is a directory, and it
> sends the command "test -d /tmp/eglot--fixture2kRNPt". It waits for the
> result in 'tramp-accept-process-output(#<process *tramp/ssh albinus@detlef*>)'
> However, 'tramp-accept-process-output' reads also the jsonrpc output by 
> 'accept-process-output(#<process EGLOT (project/(c-mode c-ts-mode c++...',

I guess that this must be due to

    (defun tramp-accept-process-output (proc &optional _timeout)
      "Like `accept-process-output' for Tramp processes.
    This is needed in order to hide `last-coding-system-used', which is set
    for process communication also.
    If the user quits via `C-g', it is propagated up to `tramp-file-name-handler'."
      (declare (advertised-calling-convention (proc) "29.2"))
      ;; There could be other processes which use the same socket for
      ;; communication.  This could block the output for the current
      ;; process.  Read such output first.  (Bug#61350)
      (when-let (((process-get proc 'shared-socket))
                 (v (process-get proc 'vector)))
        (dolist (p (delq proc (process-list)))
          (when (tramp-file-name-equal-p v (process-get p 'vector))
THIS -->    (accept-process-output p 0 nil t))))

I'm somewhat against these additional accept-process-output calls. While
we do limit them to processes from the same ssh connection, I'd argue
that usually, such processes aren't really related to the main tramp ssh
process. They could be a random *shell* process that doesn't really
belong to tramp.el any more IMO. Or in this case, eglot's jsonrpc
process.

On a bright note, I've been playing around with the original Eglot
freeze bug#61350. It seems that, given two ControlMaster ssh processes
where one refuses to produce output due to the other filling up the
shared ssh connection, we can unblock the blocked process by sending it
SIGWINCH.

Here is a POC tramp-accept-process-output implementation that does this,
and it seems to fix the freeze bug:

    (defun tramp-accept-process-output (proc &optional _timeout)
      "Like `accept-process-output' for Tramp processes.
    This is needed in order to hide `last-coding-system-used', which is set
    for process communication also.
    If the user quits via `C-g', it is propagated up to `tramp-file-name-handler'."
      (declare (advertised-calling-convention (proc) "29.2"))
      (with-current-buffer (process-buffer proc)
        (let ((inhibit-read-only t)
              last-coding-system-used
              result)
          ;; This must be protected by the "locked" property.
          (with-tramp-locked-connection proc
            ;; JUST-THIS-ONE is set due to Bug#12145.  `with-local-quit'
            ;; returns t in order to report success.
            (if (with-local-quit
                  (setq result (accept-process-output proc 0 nil t))
                  (when (and (not result) (process-get proc 'shared-socket))
                    ;; bug#62194
                    (tramp-message
                     proc 10 "%s: Trying to unblock process with SIGWINCH" proc)
                    (internal-default-signal-process proc 'sigwinch))
                  t)
                (tramp-message
                 proc 10 "%s %s %s\n%s"
                 proc (process-status proc) result (buffer-string))
              ;; Propagate quit.
              (keyboard-quit)))
          result)))

Best regards.