Subject: Re: bug#62044: 30.0.50; ERC 5.5: Auto-reconnect is broken

From: "J.P." <[email-masked]>
Date: Wed, 08 Mar 2023 08:12:14 -0800

xoddf2 <[email-masked]> writes:

> J.P. writes:
>
>> Hi xoddf2,
>>
>> [...] For starters, we need to find a recipe, all the way from
>> emacs -Q, that triggers the unwanted behavior. That way, we can dispense
>> with any possible complications arising from your init.el and any
>> third-party packages [...]
>
> After running emacs-snapshot -Q, I yanked the below Emacs Lisp code into
> the *scratch* buffer, evaluated it, then ran M-x erc.  I left server and
> port at default (irc.libera.chat and 6667, respectively), set nick to
> aoddf2, and left the server password blank.  I then joined ##test.  I
> said something in that channel from my usual client, disconnected the VM
> from the network in NetworkManager Applet, and then waited a few
> minutes.  I reconnected and then waited again.  ERC had still not
> reconnected.

Thanks for the logs and the lowdown

As with many things IRC, there are two senses of "connectivity" at play
here with regard to automatic reconnections: network and application. In
your initial report, underlying connectivity is present in some form
because ERC reaches the "Logging in as" stage and attempts to send an
application payload, though there's no telling how far it actually gets.

In your followup, network connectivity is absent due to your
"disconnecting the VM", something confirmed by the logs. While I think
this simulation is worth exploring, we probably shouldn't assume it's
failing in a meaningfully similar way to the real-life connection you're
losing to your bouncer, at least not without trying the settings you
laid out initially and also seeking a better understanding of what the
NM applet is actually doing when you flip the switch.

So to keep things sane, we should probably treat auto-reconnecting
across connectivity gaps as a wishlist item and auto-reconnecting atop
healthy connections as an existing, previously unreported bug. As for
the new feature, I've attached a POC patch that you can try if you're
willing (usage is self-explanatory, but feel free to modify or iterate
as needed). As for the other issue, I think we're going to need some
genuine session logs to really get anywhere. That is, we'll likely need
you to enable logging and tracing during a real session with your
bouncer over a hopefully not-too-prolonged period to capture an actual
reconnect sequence failing. But hold off on that for another round
unless you're feeling adventurous.

Thanks again.

>From 7c0d22d187400cf2445044f6526814a72b45a86d Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Wed, 8 Mar 2023 06:14:36 -0800
Subject: [PATCH] Add conditional erc-server-reconnect-function

* lisp/erc/erc-backend.el (erc--server-reconnect-timer,
erc-server-delayed-check-reconnect): Add possible alternate value for
option `erc-server-reconnect-function' that only attempts to reconnect
after hearing back from the server.  Also add helper variable.
---
 lisp/erc/erc-backend.el | 41 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el
index 567443f5329..d1738e4f92d 100644
--- a/lisp/erc/erc-backend.el
+++ b/lisp/erc/erc-backend.el
@@ -744,6 +744,47 @@ erc-server-delayed-reconnect
     (with-current-buffer buffer
       (erc-server-reconnect))))
 
+(defvar-local erc--server-reconnect-timeout nil)
+
+(defun erc-server-delayed-check-reconnect (buffer)
+  "Wait for internet connectivity before trying to reconnect.
+BUFFER is the server buffer for the current connection."
+  (with-current-buffer buffer
+    (setq erc--server-reconnect-timeout
+          (min 300 (* (or erc--server-reconnect-timeout
+                          erc-server-reconnect-timeout)
+                      2)))
+    (let ((reschedule
+           (lambda (proc)
+             (let ((erc-server-reconnect-timeout
+                    erc--server-reconnect-timeout))
+               (with-current-buffer buffer
+                 (delete-process proc)
+                 (erc-display-message nil 'error buffer "Nobody home...")
+                 (erc-schedule-reconnect buffer 0))))))
+      (condition-case _
+          (make-network-process
+           :name "*erc-connectivity-check"
+           :host erc-session-server
+           :service erc-session-port
+           :nowait t
+           :filter
+           (lambda (proc _)
+             (delete-process proc)
+             (with-current-buffer buffer
+               (setq erc--server-reconnect-timeout nil))
+             (run-at-time nil nil #'erc-server-delayed-reconnect buffer))
+           :sentinel
+           (lambda (cproc event)
+             (with-current-buffer buffer
+               (pcase event
+                 ("open\n"
+                  (run-at-time nil nil #'send-string
+                               cproc "PING *connect-check*\r\n"))
+                 ("connection broken by remote peer\n"
+                  (funcall reschedule cproc))))))
+        (file-error (funcall reschedule nil))))))
+
 (defun erc-server-filter-function (process string)
   "The process filter for the ERC server."
   (with-current-buffer (process-buffer process)
-- 
2.39.2