3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-12-22 18:52:41 +01:00

fix several session destruction bugs

This commit is contained in:
Shivaram Lingamneni 2019-05-08 18:14:49 -04:00
parent da656c07c8
commit 60c8f286e8
4 changed files with 71 additions and 55 deletions

View File

@ -323,8 +323,10 @@ func (client *Client) run(session *Session) {
session.resetFakelag() session.resetFakelag()
isReattach := client.Registered() isReattach := client.Registered()
if isReattach {
client.playReattachMessages(session)
} else {
// don't reset the nick timer during a reattach // don't reset the nick timer during a reattach
if !isReattach {
client.nickTimer.Initialize(client) client.nickTimer.Initialize(client)
} }
@ -386,14 +388,14 @@ func (client *Client) run(session *Session) {
break break
} else if session.client != client { } else if session.client != client {
// bouncer reattach // bouncer reattach
session.playReattachMessages()
go session.client.run(session) go session.client.run(session)
break break
} }
} }
} }
func (session *Session) playReattachMessages() { func (client *Client) playReattachMessages(session *Session) {
client.server.playRegistrationBurst(session)
for _, channel := range session.client.Channels() { for _, channel := range session.client.Channels() {
channel.playJoinForSession(session) channel.playJoinForSession(session)
} }
@ -922,14 +924,11 @@ func (client *Client) destroy(beingResumed bool, session *Session) {
// allow destroy() to execute at most once // allow destroy() to execute at most once
client.stateMutex.Lock() client.stateMutex.Lock()
nickMaskString := client.nickMaskString details := client.detailsNoMutex()
accountName := client.accountName wasReattach := session != nil && session.client != client
alreadyDestroyed := len(client.sessions) == 0
sessionRemoved := false sessionRemoved := false
var remainingSessions int var remainingSessions int
if session == nil { if session == nil {
sessionRemoved = !alreadyDestroyed
sessionsToDestroy = client.sessions sessionsToDestroy = client.sessions
client.sessions = nil client.sessions = nil
remainingSessions = 0 remainingSessions = 0
@ -939,33 +938,46 @@ func (client *Client) destroy(beingResumed bool, session *Session) {
sessionsToDestroy = []*Session{session} sessionsToDestroy = []*Session{session}
} }
} }
var quitMessage string
if 0 < len(sessionsToDestroy) {
quitMessage = sessionsToDestroy[0].quitMessage
}
client.stateMutex.Unlock() client.stateMutex.Unlock()
if alreadyDestroyed || !sessionRemoved { if len(sessionsToDestroy) == 0 {
return return
} }
// destroy all applicable sessions:
var quitMessage string
for _, session := range sessionsToDestroy { for _, session := range sessionsToDestroy {
if session.client != client { if session.client != client {
// session has been attached to a new client; do not destroy it // session has been attached to a new client; do not destroy it
continue continue
} }
session.idletimer.Stop() session.idletimer.Stop()
session.socket.Close()
// send quit/error message to client if they haven't been sent already // send quit/error message to client if they haven't been sent already
client.Quit("", session) client.Quit("", session)
quitMessage = session.quitMessage
session.socket.Close()
// remove from connection limits
var source string
if client.isTor {
client.server.torLimiter.RemoveClient()
source = "tor"
} else {
ip := session.realIP
if session.proxiedIP != nil {
ip = session.proxiedIP
}
client.server.connectionLimiter.RemoveClient(ip)
source = ip.String()
}
client.server.logger.Info("localconnect-ip", fmt.Sprintf("disconnecting session of %s from %s", details.nick, source))
} }
// ok, now destroy the client, unless it still has sessions:
if remainingSessions != 0 { if remainingSessions != 0 {
return return
} }
details := client.Details()
// see #235: deduplicating the list of PART recipients uses (comparatively speaking) // see #235: deduplicating the list of PART recipients uses (comparatively speaking)
// a lot of RAM, so limit concurrency to avoid thrashing // a lot of RAM, so limit concurrency to avoid thrashing
client.server.semaphores.ClientDestroy.Acquire() client.server.semaphores.ClientDestroy.Acquire()
@ -973,38 +985,35 @@ func (client *Client) destroy(beingResumed bool, session *Session) {
if beingResumed { if beingResumed {
client.server.logger.Debug("quit", fmt.Sprintf("%s is being resumed", details.nick)) client.server.logger.Debug("quit", fmt.Sprintf("%s is being resumed", details.nick))
} else { } else if !wasReattach {
client.server.logger.Debug("quit", fmt.Sprintf("%s is no longer on the server", details.nick)) client.server.logger.Debug("quit", fmt.Sprintf("%s is no longer on the server", details.nick))
} }
if !beingResumed { registered := client.Registered()
client.server.whoWas.Append(details.WhoWas) if !beingResumed && registered {
} client.server.whoWas.Append(client.WhoWas())
// remove from connection limits
if client.isTor {
client.server.torLimiter.RemoveClient()
} else {
client.server.connectionLimiter.RemoveClient(client.IP())
} }
client.server.resumeManager.Delete(client) client.server.resumeManager.Delete(client)
// alert monitors // alert monitors
if registered {
client.server.monitorManager.AlertAbout(client, false) client.server.monitorManager.AlertAbout(client, false)
}
// clean up monitor state // clean up monitor state
client.server.monitorManager.RemoveAll(client) client.server.monitorManager.RemoveAll(client)
splitQuitMessage := utils.MakeSplitMessage(quitMessage, true) splitQuitMessage := utils.MakeSplitMessage(quitMessage, true)
// clean up channels // clean up channels
// (note that if this is a reattach, client has no channels and therefore no friends)
friends := make(ClientSet) friends := make(ClientSet)
for _, channel := range client.Channels() { for _, channel := range client.Channels() {
if !beingResumed { if !beingResumed {
channel.Quit(client) channel.Quit(client)
channel.history.Add(history.Item{ channel.history.Add(history.Item{
Type: history.Quit, Type: history.Quit,
Nick: nickMaskString, Nick: details.nickMask,
AccountName: accountName, AccountName: details.accountName,
Message: splitQuitMessage, Message: splitQuitMessage,
}) })
} }

View File

@ -160,9 +160,7 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
if !currentClient.AddSession(session) { if !currentClient.AddSession(session) {
return errNicknameInUse return errNicknameInUse
} }
// successful reattach. temporarily assign them the nick they'll have going forward // successful reattach!
// (the current `client` will be discarded at the end of command execution)
client.updateNick(currentClient.Nick(), newcfnick, newSkeleton)
return nil return nil
} }
// analogous checks for skeletons // analogous checks for skeletons

View File

@ -329,7 +329,10 @@ func (client *Client) WhoWas() (result WhoWas) {
func (client *Client) Details() (result ClientDetails) { func (client *Client) Details() (result ClientDetails) {
client.stateMutex.RLock() client.stateMutex.RLock()
defer client.stateMutex.RUnlock() defer client.stateMutex.RUnlock()
return client.detailsNoMutex()
}
func (client *Client) detailsNoMutex() (result ClientDetails) {
result.nick = client.nick result.nick = client.nick
result.nickCasefolded = client.nickCasefolded result.nickCasefolded = client.nickCasefolded
result.username = client.username result.username = client.username

View File

@ -413,20 +413,30 @@ func (server *Server) tryRegister(c *Client, session *Session) {
} }
} }
reattached := session.client != c if session.client != c {
// reattached, bail out.
// we'll play the reg burst later, on the new goroutine associated with
// (thisSession, otherClient). This is to avoid having to transfer state
// like nickname, hostname, etc. to show the correct values in the reg burst.
return
}
if !reattached {
// registration has succeeded: // registration has succeeded:
c.SetRegistered() c.SetRegistered()
// count new user in statistics // count new user in statistics
server.stats.ChangeTotal(1) server.stats.ChangeTotal(1)
if !resumed { server.playRegistrationBurst(session)
if resumed {
c.tryResumeChannels()
} else {
server.monitorManager.AlertAbout(c, true) server.monitorManager.AlertAbout(c, true)
} }
} }
func (server *Server) playRegistrationBurst(session *Session) {
c := session.client
// continue registration // continue registration
d := c.Details() d := c.Details()
server.logger.Info("localconnect", fmt.Sprintf("Client connected [%s] [u:%s] [r:%s]", d.nick, d.username, d.realname)) server.logger.Info("localconnect", fmt.Sprintf("Client connected [%s] [u:%s] [r:%s]", d.nick, d.username, d.realname))
@ -435,11 +445,11 @@ func (server *Server) tryRegister(c *Client, session *Session) {
// send welcome text // send welcome text
//NOTE(dan): we specifically use the NICK here instead of the nickmask //NOTE(dan): we specifically use the NICK here instead of the nickmask
// see http://modern.ircdocs.horse/#rplwelcome-001 for details on why we avoid using the nickmask // see http://modern.ircdocs.horse/#rplwelcome-001 for details on why we avoid using the nickmask
c.Send(nil, server.name, RPL_WELCOME, d.nick, fmt.Sprintf(c.t("Welcome to the Internet Relay Network %s"), d.nick)) session.Send(nil, server.name, RPL_WELCOME, d.nick, fmt.Sprintf(c.t("Welcome to the Internet Relay Network %s"), d.nick))
c.Send(nil, server.name, RPL_YOURHOST, d.nick, fmt.Sprintf(c.t("Your host is %[1]s, running version %[2]s"), server.name, Ver)) session.Send(nil, server.name, RPL_YOURHOST, d.nick, fmt.Sprintf(c.t("Your host is %[1]s, running version %[2]s"), server.name, Ver))
c.Send(nil, server.name, RPL_CREATED, d.nick, fmt.Sprintf(c.t("This server was created %s"), server.ctime.Format(time.RFC1123))) session.Send(nil, server.name, RPL_CREATED, d.nick, fmt.Sprintf(c.t("This server was created %s"), server.ctime.Format(time.RFC1123)))
//TODO(dan): Look at adding last optional [<channel modes with a parameter>] parameter //TODO(dan): Look at adding last optional [<channel modes with a parameter>] parameter
c.Send(nil, server.name, RPL_MYINFO, d.nick, server.name, Ver, supportedUserModesString, supportedChannelModesString) session.Send(nil, server.name, RPL_MYINFO, d.nick, server.name, Ver, supportedUserModesString, supportedChannelModesString)
rb := NewResponseBuffer(session) rb := NewResponseBuffer(session)
c.RplISupport(rb) c.RplISupport(rb)
@ -448,14 +458,10 @@ func (server *Server) tryRegister(c *Client, session *Session) {
modestring := c.ModeString() modestring := c.ModeString()
if modestring != "+" { if modestring != "+" {
c.Send(nil, d.nickMask, RPL_UMODEIS, d.nick, c.ModeString()) session.Send(nil, d.nickMask, RPL_UMODEIS, d.nick, modestring)
} }
if server.logger.IsLoggingRawIO() { if server.logger.IsLoggingRawIO() {
c.Notice(c.t("This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.")) session.Send(nil, c.server.name, "NOTICE", d.nick, c.t("This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect."))
}
if resumed {
c.tryResumeChannels()
} }
} }