Improve auto-reconnect visibility in ERC

* lisp/erc/erc-backend.el (erc--server-reconnect-timer): New variable.
(erc-server-reconnect-function): New user option.
(erc-process-sentinel-2): Display time remaining until next
reconnection attempt.  Also remove condition case and move bulk of
else condition logic to `erc-schedule-reconnect'.  More importantly,
no longer set `erc--server-reconnecting here').
(erc-server-connect): Initialize `erc--server-reconnect-timer' to nil.
(erc-server-reconnect): Set `erc-server--reconnecting' here.
(erc--mode-line-process-reconnecting): New constant to store value for
"reconnect" state of `mode-line-process'.
(erc--cancel-auto-reconnect-timer): New function to cancel
auto-reconnect timer and print message.
(erc-schedule-reconnect): New function for scheduling another
reconnect attempt.

* lisp/erc/erc.el (erc-open): Only update mode line for target
buffers. For server buffers, let `erc-login' and/or process sentinels
take care of it.
(erc--cmd-reconnect, erc-cmd-RECONNECT): Rename latter to former, a
new function, but repurpose existing to recognize newly allowed
additional arguments and act accordingly.  In new internal function,
cancel an existing auto-reconnect timer, if any, before proceeding.
Defer to `erc-server-reconnect' to set `erc--server-reconnecting'.
Fix `with-suppressed-warnings' form.
(erc-update-mode-line-buffer): Show "reconnecting in Ns" for
`mode-line-process' when awaiting an automatic reconnect attempt.
(erc-message-english-reconnecting,
erc-message-english-reconnect-canceled): Add new message functions to
English catalog.

* lisp/erc/erc-pcomplete.el (pcomplete/erc-mode/RECONNECT): Perform
completion for newly subcommand-aware `erc-cmd-RECONNECT'.

* lisp/erc/erc-scenarios-base-reconnect
(erc-scenarios-base-cancel-reconnect): Add new test case for canceling
reconnect timers.  (Bug#58840.)
This commit is contained in:
F. Jason Park 2022-10-27 00:21:10 -07:00
parent 4351fb7161
commit e7f2f6cd92
4 changed files with 137 additions and 35 deletions

View file

@ -299,6 +299,9 @@ function `erc-server-process-alive' instead.")
(defvar-local erc--server-last-reconnect-count 0
"Snapshot of reconnect count when the connection was established.")
(defvar-local erc--server-reconnect-timer nil
"Auto-reconnect timer for a network context.")
(defvar-local erc-server-quitting nil
"Non-nil if the user requests a quit.")
@ -401,6 +404,16 @@ This only has an effect if `erc-server-auto-reconnect' is non-nil."
If a key is pressed while ERC is waiting, it will stop waiting."
:type 'number)
(defcustom erc-server-reconnect-function 'erc-server-delayed-reconnect
"Function called by the reconnect timer to create a new connection.
Called with a server buffer as its only argument. Potential uses
include exponential backoff and probing for connectivity prior to
dialing. Use `erc-schedule-reconnect' to instead try again later
and optionally alter the attempts tally."
:package-version '(ERC . "5.4.1") ; FIXME on next release
:type '(choice (function-item erc-server-delayed-reconnect)
function))
(defcustom erc-split-line-length 440
"The maximum length of a single message.
If a message exceeds this size, it is broken into multiple ones.
@ -645,7 +658,8 @@ TLS (see `erc-session-client-certificate' for more details)."
(setq erc-server-process process)
(setq erc-server-quitting nil)
(setq erc-server-reconnecting nil
erc--server-reconnecting nil)
erc--server-reconnecting nil
erc--server-reconnect-timer nil)
(setq erc-server-timed-out nil)
(setq erc-server-banned nil)
(setq erc-server-error-occurred nil)
@ -686,6 +700,7 @@ Make sure you are in an ERC buffer when running this."
(with-current-buffer buffer
(erc-update-mode-line)
(erc-set-active-buffer (current-buffer))
(setq erc--server-reconnecting t)
(setq erc-server-last-sent-time 0)
(setq erc-server-lines-sent 0)
(let ((erc-server-connect-function (or erc-session-connector
@ -758,37 +773,59 @@ EVENT is the message received from the closed connection process."
erc-server-reconnecting)
(erc--server-reconnect-p event)))
(defconst erc--mode-line-process-reconnecting
'(:eval (erc-with-server-buffer
(and erc--server-reconnect-timer
(format ": reconnecting in %.1fs"
(- (timer-until erc--server-reconnect-timer
(current-time)))))))
"Mode-line construct showing seconds until next reconnect attempt.
Move point around to refresh.")
(defun erc--cancel-auto-reconnect-timer ()
(when erc--server-reconnect-timer
(cancel-timer erc--server-reconnect-timer)
(erc-display-message nil 'notice nil 'reconnect-canceled
?u (buffer-name)
?c (- (timer-until erc--server-reconnect-timer
(current-time))))
(setq erc--server-reconnect-timer nil)
(erc-update-mode-line)))
(defun erc-schedule-reconnect (buffer &optional incr)
"Create and return a reconnect timer for BUFFER.
When `erc-server-reconnect-attempts' is a number, increment
`erc-server-reconnect-count' by INCR unconditionally."
(let ((count (and (integerp erc-server-reconnect-attempts)
(- erc-server-reconnect-attempts
(cl-incf erc-server-reconnect-count (or incr 1))))))
(erc-display-message nil 'error (current-buffer) 'reconnecting
?m erc-server-reconnect-timeout
?i (if count erc-server-reconnect-count "N")
?n (if count erc-server-reconnect-attempts "A"))
(setq erc-server-reconnecting nil
erc--server-reconnect-timer
(run-at-time erc-server-reconnect-timeout nil
erc-server-reconnect-function buffer))))
(defun erc-process-sentinel-2 (event buffer)
"Called when `erc-process-sentinel-1' has detected an unexpected disconnect."
(if (not (buffer-live-p buffer))
(erc-update-mode-line)
(when (buffer-live-p buffer)
(with-current-buffer buffer
(let ((reconnect-p (erc--server-reconnect-p event)) message delay)
(let ((reconnect-p (erc--server-reconnect-p event)) message)
(setq message (if reconnect-p 'disconnected 'disconnected-noreconnect))
(erc-display-message nil 'error (current-buffer) message)
(if (not reconnect-p)
;; terminate, do not reconnect
(progn
(setq erc--server-reconnecting nil)
(setq erc--server-reconnecting nil
erc--server-reconnect-timer nil)
(erc-display-message nil 'error (current-buffer)
'terminated ?e event)
;; Update mode line indicators
(erc-update-mode-line)
(set-buffer-modified-p nil))
;; reconnect
(condition-case nil
(progn
(setq erc-server-reconnecting nil
erc--server-reconnecting t
erc-server-reconnect-count (1+ erc-server-reconnect-count))
(setq delay erc-server-reconnect-timeout)
(run-at-time delay nil
#'erc-server-delayed-reconnect buffer))
(error (unless (integerp erc-server-reconnect-attempts)
(message "%s ... %s"
"Reconnecting until we succeed"
"kill the ERC server buffer to stop"))
(erc-server-delayed-reconnect buffer))))))))
(erc-schedule-reconnect buffer))))
(erc-update-mode-line)))
(defun erc-process-sentinel-1 (event buffer)
"Called when `erc-process-sentinel' has decided that we're disconnecting.

View file

@ -179,6 +179,10 @@ for use on `completion-at-point-function'."
(defun pcomplete/erc-mode/UNIGNORE ()
(pcomplete-here (erc-with-server-buffer erc-ignore-list)))
(defun pcomplete/erc-mode/RECONNECT ()
(pcomplete-here '("cancel"))
(pcomplete-opt "a"))
;;; Functions that provide possible completions.
(defun pcomplete-erc-commands ()

View file

@ -2032,12 +2032,12 @@ Returns the buffer for the given server or channel."
;; Saving log file on exit
(run-hook-with-args 'erc-connect-pre-hook buffer)
(when connect
(erc-server-connect erc-session-server
erc-session-port
buffer
erc-session-client-certificate))
(erc-update-mode-line)
(if connect
(erc-server-connect erc-session-server
erc-session-port
buffer
erc-session-client-certificate)
(erc-update-mode-line))
;; Now display the buffer in a window as per user wishes.
(unless (eq buffer old-buffer)
@ -3804,17 +3804,17 @@ the message given by REASON."
(put 'erc-cmd-GQUIT 'do-not-parse-args t)
(put 'erc-cmd-GQUIT 'process-not-needed t)
(defun erc-cmd-RECONNECT ()
"Try to reconnect to the current IRC server."
(defun erc--cmd-reconnect ()
(let ((buffer (erc-server-buffer))
(process nil))
(unless (buffer-live-p buffer)
(setq buffer (current-buffer)))
(with-current-buffer buffer
(when erc--server-reconnect-timer
(erc--cancel-auto-reconnect-timer))
(setq erc-server-quitting nil)
(with-suppressed-warnings ((obsolete erc-server-reconnecting))
(setq erc-server-reconnecting t))
(setq erc--server-reconnecting t)
(setq erc-server-reconnect-count 0)
(setq process (get-buffer-process (erc-server-buffer)))
(when process
@ -3828,6 +3828,18 @@ the message given by REASON."
(setq erc--server-reconnecting nil
erc-server-reconnecting nil)))))
t)
(defun erc-cmd-RECONNECT (&rest args)
"Try reconnecting to the current IRC server.
Alternatively, CANCEL a scheduled attempt for either the current
connection or, with -A, all applicable connections.
\(fn [CANCEL [-A]])"
(pcase args
(`("cancel" "-a") (erc-buffer-filter #'erc--cancel-auto-reconnect-timer))
(`("cancel") (erc-with-server-buffer (erc--cancel-auto-reconnect-timer)))
(_ (erc--cmd-reconnect))))
(put 'erc-cmd-RECONNECT 'process-not-needed t)
(defun erc-cmd-SERVER (server)
@ -6713,11 +6725,12 @@ shortened server name instead."
(?s . ,(erc-format-target-and/or-server))
(?S . ,(erc-format-target-and/or-network))
(?t . ,(erc-format-target))))
(process-status (cond ((and (erc-server-process-alive)
(not erc-server-connected))
":connecting")
((erc-server-process-alive)
"")
(process-status (cond ((erc-server-process-alive buffer)
(unless erc-server-connected
": connecting"))
((erc-with-server-buffer
erc--server-reconnect-timer)
erc--mode-line-process-reconnecting)
(t
": CLOSED")))
(face (cond ((eq erc-header-line-face-method nil)
@ -6728,7 +6741,7 @@ shortened server name instead."
'erc-header-line))))
(setq mode-line-buffer-identification
(list (format-spec erc-mode-line-format spec)))
(setq mode-line-process (list process-status))
(setq mode-line-process process-status)
(let ((header (if erc-header-line-format
(format-spec erc-header-line-format spec)
nil)))
@ -6913,6 +6926,8 @@ All windows are opened in the current frame."
(disconnected . "\n\nConnection failed! Re-establishing connection...\n")
(disconnected-noreconnect
. "\n\nConnection failed! Not re-establishing connection.\n")
(reconnecting . "Reconnecting in %ms: attempt %i/%n ...")
(reconnect-canceled . "Canceled %u reconnect timer with %cs to go...")
(finished . "\n\n*** ERC finished ***\n")
(terminated . "\n\n*** ERC terminated: %e\n")
(login . "Logging in as `%n'...")

View file

@ -224,4 +224,50 @@
(with-current-buffer "#chan"
(funcall expect 10 "here comes the lady")))))
(ert-deftest erc-scenarios-base-cancel-reconnect ()
:tags '(:expensive-test)
(erc-scenarios-common-with-cleanup
((erc-scenarios-common-dialog "base/reconnect")
(dumb-server (erc-d-run "localhost" t 'timer 'timer 'timer-last))
(port (process-contact dumb-server :service))
(expect (erc-d-t-make-expecter))
(erc-server-auto-reconnect t)
erc-autojoin-channels-alist
erc-server-buffer)
(ert-info ("Connect to foonet")
(setq erc-server-buffer (erc :server "127.0.0.1"
:port port
:nick "tester"
:password "changeme"
:full-name "tester"))
(with-current-buffer erc-server-buffer
(should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
(ert-info ("Two connection attempts, all stymied")
(with-current-buffer erc-server-buffer
(ert-info ("First two attempts behave normally")
(dotimes (n 2)
(ert-info ((format "Initial attempt %d" (1+ n)))
(funcall expect 3 "Opening connection")
(funcall expect 2 "Password incorrect")
(funcall expect 2 "Connection failed!")
(funcall expect 2 "Re-establishing connection"))))
(ert-info ("/RECONNECT cancels timer but still attempts to connect")
(erc-cmd-RECONNECT)
(funcall expect 2 "Canceled")
(funcall expect 3 "Opening connection")
(funcall expect 2 "Password incorrect")
(funcall expect 2 "Connection failed!")
(funcall expect 2 "Re-establishing connection"))
(ert-info ("Explicitly cancel timer")
(erc-cmd-RECONNECT "cancel")
(funcall expect 2 "Canceled")
(erc-d-t-absent-for 1 "Opening connection" (point)))))
(ert-info ("Server buffer is unique and temp name is absent")
(should (equal (list (get-buffer (format "127.0.0.1:%d" port)))
(erc-scenarios-common-buflist "127.0.0.1"))))))
;;; erc-scenarios-base-reconnect.el ends here