From eb941d2d2b8cf51f9ad8aafbc84bfe14531ee5d6 Mon Sep 17 00:00:00 2001 From: delthas Date: Sat, 4 Apr 2020 04:48:25 +0200 Subject: [PATCH] Send one NOTICE on new upstream disconnect/connect errors In order to notify the user when we are disconnected from a network (either due to an error, or due a QUIT), and when we fail reconnecting, this commit adds support for sending a short NOTICE message from the service user to all relevant downstreams. The last error is stored, and cleared on successful connection, to ensure that the user is *not* flooded with identical connection error messages, which can often happen when a server is down. No lock is needed on lastError because it is only read and modified from the user goroutine. Closes: https://todo.sr.ht/~emersion/soju/27 --- service.go | 8 ++++++++ upstream.go | 7 +------ user.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/service.go b/service.go index 0756804..1157239 100644 --- a/service.go +++ b/service.go @@ -21,6 +21,14 @@ type serviceCommand struct { children serviceCommandSet } +func sendServiceNOTICE(dc *downstreamConn, text string) { + dc.SendMessage(&irc.Message{ + Prefix: &irc.Prefix{Name: serviceNick}, + Command: "NOTICE", + Params: []string{dc.nick, text}, + }) +} + func sendServicePRIVMSG(dc *downstreamConn, text string) { dc.SendMessage(&irc.Message{ Prefix: &irc.Prefix{Name: serviceNick}, diff --git a/upstream.go b/upstream.go index 2d4e9bb..5a002b3 100644 --- a/upstream.go +++ b/upstream.go @@ -103,12 +103,7 @@ func connectToUpstream(network *network) (*upstreamConn, error) { } func (uc *upstreamConn) forEachDownstream(f func(*downstreamConn)) { - uc.user.forEachDownstream(func(dc *downstreamConn) { - if dc.network != nil && dc.network != uc.network { - return - } - f(dc) - }) + uc.network.forEachDownstream(f) } func (uc *upstreamConn) forEachDownstreamByID(id uint64, f func(*downstreamConn)) { diff --git a/user.go b/user.go index 04c77a9..becf5b6 100644 --- a/user.go +++ b/user.go @@ -1,6 +1,7 @@ package soju import ( + "fmt" "sync" "time" @@ -14,6 +15,11 @@ type eventUpstreamMessage struct { uc *upstreamConn } +type eventUpstreamConnectionError struct { + net *network + err error +} + type eventUpstreamConnected struct { uc *upstreamConn } @@ -22,6 +28,11 @@ type eventUpstreamDisconnected struct { uc *upstreamConn } +type eventUpstreamError struct { + uc *upstreamConn + err error +} + type eventDownstreamMessage struct { msg *irc.Message dc *downstreamConn @@ -41,7 +52,8 @@ type network struct { ring *Ring stopped chan struct{} - history map[string]uint64 + history map[string]uint64 + lastError error lock sync.Mutex conn *upstreamConn @@ -57,6 +69,15 @@ func newNetwork(user *user, record *Network) *network { } } +func (net *network) forEachDownstream(f func(*downstreamConn)) { + net.user.forEachDownstream(func(dc *downstreamConn) { + if dc.network != nil && dc.network != net { + return + } + f(dc) + }) +} + func (net *network) run() { var lastTry time.Time for { @@ -77,12 +98,14 @@ func (net *network) run() { uc, err := connectToUpstream(net) if err != nil { net.user.srv.Logger.Printf("failed to connect to upstream server %q: %v", net.Addr, err) + net.user.events <- eventUpstreamConnectionError{net, fmt.Errorf("failed to connect: %v", err)} continue } uc.register() if err := uc.runUntilRegistered(); err != nil { uc.logger.Printf("failed to register: %v", err) + net.user.events <- eventUpstreamConnectionError{net, fmt.Errorf("failed to register: %v", err)} uc.Close() continue } @@ -90,6 +113,7 @@ func (net *network) run() { net.user.events <- eventUpstreamConnected{uc} if err := uc.readMessages(net.user.events); err != nil { uc.logger.Printf("failed to handle messages: %v", err) + net.user.events <- eventUpstreamError{uc, fmt.Errorf("failed to handle messages: %v", err)} } uc.Close() net.user.events <- eventUpstreamDisconnected{uc} @@ -200,6 +224,11 @@ func (u *user) run() { uc.network.lock.Unlock() uc.updateAway() + + uc.forEachDownstream(func(dc *downstreamConn) { + sendServiceNOTICE(dc, fmt.Sprintf("connected to %s", uc.network.Name)) + }) + uc.network.lastError = nil case eventUpstreamDisconnected: uc := e.uc @@ -214,6 +243,28 @@ func (u *user) run() { } uc.endPendingLISTs(true) + + if uc.network.lastError == nil { + uc.forEachDownstream(func(dc *downstreamConn) { + sendServiceNOTICE(dc, fmt.Sprintf("disconnected from %s", uc.network.Name)) + }) + } + case eventUpstreamConnectionError: + net := e.net + + if net.lastError == nil || net.lastError.Error() != e.err.Error() { + net.forEachDownstream(func(dc *downstreamConn) { + sendServiceNOTICE(dc, fmt.Sprintf("failed connecting/registering to %s: %v", net.Name, e.err)) + }) + } + net.lastError = e.err + case eventUpstreamError: + uc := e.uc + + uc.forEachDownstream(func(dc *downstreamConn) { + sendServiceNOTICE(dc, fmt.Sprintf("disconnected from %s: %v", uc.network.Name, e.err)) + }) + uc.network.lastError = e.err case eventUpstreamMessage: msg, uc := e.msg, e.uc if uc.isClosed() {