forked from External/ergo
fix #1229
This commit is contained in:
parent
12bcba01cd
commit
a3e5c9e98c
3 changed files with 104 additions and 182 deletions
109
irc/client.go
109
irc/client.go
|
|
@ -40,6 +40,22 @@ const (
|
|||
lastSeenWriteInterval = time.Hour
|
||||
)
|
||||
|
||||
const (
|
||||
// RegisterTimeout is how long clients have to register before we disconnect them
|
||||
RegisterTimeout = time.Minute
|
||||
// DefaultIdleTimeout is how long without traffic before we send the client a PING
|
||||
DefaultIdleTimeout = time.Minute + 30*time.Second
|
||||
// For Tor clients, we send a PING at least every 30 seconds, as a workaround for this bug
|
||||
// (single-onion circuits will close unless the client sends data once every 60 seconds):
|
||||
// https://bugs.torproject.org/29665
|
||||
TorIdleTimeout = time.Second * 30
|
||||
// This is how long a client gets without sending any message, including the PONG to our
|
||||
// PING, before we disconnect them:
|
||||
DefaultTotalTimeout = 2*time.Minute + 30*time.Second
|
||||
// Resumeable clients (clients who have negotiated caps.Resume) get longer:
|
||||
ResumeableTotalTimeout = 3*time.Minute + 30*time.Second
|
||||
)
|
||||
|
||||
// ResumeDetails is a place to stash data at various stages of
|
||||
// the resume process: when handling the RESUME command itself,
|
||||
// when completing the registration, and when rejoining channels.
|
||||
|
|
@ -83,6 +99,7 @@ type Client struct {
|
|||
realname string
|
||||
realIP net.IP
|
||||
registered bool
|
||||
registrationTimer *time.Timer
|
||||
resumeID string
|
||||
server *Server
|
||||
skeleton string
|
||||
|
|
@ -123,7 +140,10 @@ type Session struct {
|
|||
deviceID string
|
||||
|
||||
ctime time.Time
|
||||
lastActive time.Time
|
||||
lastActive time.Time // last non-CTCP PRIVMSG sent; updates publicly visible idle time
|
||||
lastTouch time.Time // last line sent; updates timer for idle timeouts
|
||||
idleTimer *time.Timer
|
||||
pingSent bool // we sent PING to a putatively idle connection and we're waiting for PONG
|
||||
|
||||
socket *Socket
|
||||
realIP net.IP
|
||||
|
|
@ -131,7 +151,6 @@ type Session struct {
|
|||
rawHostname string
|
||||
isTor bool
|
||||
|
||||
idletimer IdleTimer
|
||||
fakelag Fakelag
|
||||
deferredFakelagCount int
|
||||
destroyed uint32
|
||||
|
|
@ -342,7 +361,6 @@ func (server *Server) RunClient(conn IRCConn) {
|
|||
}
|
||||
client.sessions = []*Session{session}
|
||||
|
||||
session.idletimer.Initialize(session)
|
||||
session.resetFakelag()
|
||||
|
||||
if wConn.Secure {
|
||||
|
|
@ -363,6 +381,7 @@ func (server *Server) RunClient(conn IRCConn) {
|
|||
}
|
||||
}
|
||||
|
||||
client.registrationTimer = time.AfterFunc(RegisterTimeout, client.handleRegisterTimeout)
|
||||
server.stats.Add()
|
||||
client.run(session)
|
||||
}
|
||||
|
|
@ -605,7 +624,7 @@ func (client *Client) run(session *Session) {
|
|||
|
||||
isReattach := client.Registered()
|
||||
if isReattach {
|
||||
session.idletimer.Touch()
|
||||
client.Touch(session)
|
||||
if session.resumeDetails != nil {
|
||||
session.playResume()
|
||||
session.resumeDetails = nil
|
||||
|
|
@ -739,6 +758,7 @@ func (client *Client) Touch(session *Session) {
|
|||
client.lastSeenLastWrite = now
|
||||
}
|
||||
}
|
||||
client.updateIdleTimer(session, now)
|
||||
client.stateMutex.Unlock()
|
||||
if markDirty {
|
||||
client.markDirty(IncludeLastSeen)
|
||||
|
|
@ -763,6 +783,71 @@ func (client *Client) setLastSeen(now time.Time, deviceID string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (client *Client) updateIdleTimer(session *Session, now time.Time) {
|
||||
session.lastTouch = now
|
||||
session.pingSent = false
|
||||
|
||||
if session.idleTimer == nil {
|
||||
pingTimeout := DefaultIdleTimeout
|
||||
if session.isTor {
|
||||
pingTimeout = TorIdleTimeout
|
||||
}
|
||||
session.idleTimer = time.AfterFunc(pingTimeout, session.handleIdleTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func (session *Session) handleIdleTimeout() {
|
||||
totalTimeout := DefaultTotalTimeout
|
||||
if session.capabilities.Has(caps.Resume) {
|
||||
totalTimeout = ResumeableTotalTimeout
|
||||
}
|
||||
pingTimeout := DefaultIdleTimeout
|
||||
if session.isTor {
|
||||
pingTimeout = TorIdleTimeout
|
||||
}
|
||||
|
||||
session.client.stateMutex.Lock()
|
||||
now := time.Now()
|
||||
timeUntilDestroy := session.lastTouch.Add(totalTimeout).Sub(now)
|
||||
timeUntilPing := session.lastTouch.Add(pingTimeout).Sub(now)
|
||||
shouldDestroy := session.pingSent && timeUntilDestroy <= 0
|
||||
shouldSendPing := !session.pingSent && timeUntilPing <= 0
|
||||
if !shouldDestroy {
|
||||
if shouldSendPing {
|
||||
session.pingSent = true
|
||||
}
|
||||
// check in again at the minimum of these 3 possible intervals:
|
||||
// 1. the ping timeout (assuming we PING and they reply immediately with PONG)
|
||||
// 2. the next time we would send PING (if they don't send any more lines)
|
||||
// 3. the next time we would destroy (if they don't send any more lines)
|
||||
nextTimeout := pingTimeout
|
||||
if 0 < timeUntilPing && timeUntilPing < nextTimeout {
|
||||
nextTimeout = timeUntilPing
|
||||
}
|
||||
if 0 < timeUntilDestroy && timeUntilDestroy < nextTimeout {
|
||||
nextTimeout = timeUntilDestroy
|
||||
}
|
||||
session.idleTimer.Stop()
|
||||
session.idleTimer.Reset(nextTimeout)
|
||||
}
|
||||
session.client.stateMutex.Unlock()
|
||||
|
||||
if shouldDestroy {
|
||||
session.client.Quit(fmt.Sprintf("Ping timeout: %v", totalTimeout), session)
|
||||
session.client.destroy(session)
|
||||
} else if shouldSendPing {
|
||||
session.Ping()
|
||||
}
|
||||
}
|
||||
|
||||
func (session *Session) stopIdleTimer() {
|
||||
session.client.stateMutex.Lock()
|
||||
defer session.client.stateMutex.Unlock()
|
||||
if session.idleTimer != nil {
|
||||
session.idleTimer.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// Ping sends the client a PING message.
|
||||
func (session *Session) Ping() {
|
||||
session.Send(nil, "", "PING", session.client.Nick())
|
||||
|
|
@ -1119,6 +1204,10 @@ func (client *Client) SetNick(nick, nickCasefolded, skeleton string) (success bo
|
|||
} else if !client.registered {
|
||||
// XXX test this before setting it to avoid annoying the race detector
|
||||
client.registered = true
|
||||
if client.registrationTimer != nil {
|
||||
client.registrationTimer.Stop()
|
||||
client.registrationTimer = nil
|
||||
}
|
||||
}
|
||||
client.nick = nick
|
||||
client.nickCasefolded = nickCasefolded
|
||||
|
|
@ -1294,6 +1383,11 @@ func (client *Client) destroy(session *Session) {
|
|||
client.awayMessage = awayMessage
|
||||
}
|
||||
|
||||
if client.registrationTimer != nil {
|
||||
// unconditionally stop; if the client is still unregistered it must be destroyed
|
||||
client.registrationTimer.Stop()
|
||||
}
|
||||
|
||||
client.stateMutex.Unlock()
|
||||
|
||||
// XXX there is no particular reason to persist this state here rather than
|
||||
|
|
@ -1311,7 +1405,7 @@ func (client *Client) destroy(session *Session) {
|
|||
// session has been attached to a new client; do not destroy it
|
||||
continue
|
||||
}
|
||||
session.idletimer.Stop()
|
||||
session.stopIdleTimer()
|
||||
// send quit/error message to client if they haven't been sent already
|
||||
client.Quit("", session)
|
||||
quitMessage = session.quitMessage
|
||||
|
|
@ -1693,6 +1787,11 @@ func (client *Client) historyStatus(config *Config) (status HistoryStatus, targe
|
|||
return
|
||||
}
|
||||
|
||||
func (client *Client) handleRegisterTimeout() {
|
||||
client.Quit(fmt.Sprintf("Registration timeout: %v", RegisterTimeout), nil)
|
||||
client.destroy(nil)
|
||||
}
|
||||
|
||||
func (client *Client) copyLastSeen() (result map[string]time.Time) {
|
||||
client.stateMutex.RLock()
|
||||
defer client.stateMutex.RUnlock()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue