From 300d02bd9c1e2b0dd90d8b7a18a957607698ce89 Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Sun, 26 Mar 2017 22:11:09 +1000 Subject: [PATCH] channel: Kill a race condition that locked up the server. Specifically, if you joined a channel while someone else was trying to part. the Join method would grab the lock, the Quit method would queue to grab the lock, the Join method would unlock and then try to regrab the lock, and it would get into a situation where nobody would have the lock and everyone would be waiting for it. This caused weird oddities with clients. --- irc/channel.go | 23 +++++++++++------------ irc/client.go | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/irc/channel.go b/irc/channel.go index b62fb5af..bcd20921 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -221,11 +221,7 @@ func (channel *Channel) modeStringNoLock(client *Client) (str string) { } func (channel *Channel) IsFull() bool { - channel.membersMutex.RLock() - defer channel.membersMutex.RUnlock() - - return (channel.userLimit > 0) && - (uint64(len(channel.members)) >= channel.userLimit) + return (channel.userLimit > 0) && (uint64(len(channel.members)) >= channel.userLimit) } func (channel *Channel) CheckKey(key string) bool { @@ -234,11 +230,11 @@ func (channel *Channel) CheckKey(key string) bool { func (channel *Channel) Join(client *Client, key string) { channel.membersMutex.Lock() + defer channel.membersMutex.Unlock() if channel.members.Has(client) { - // already joined, no message? + // already joined, no message needs to be sent return } - channel.membersMutex.Unlock() if channel.IsFull() { client.Send(nil, client.server.name, ERR_CHANNELISFULL, channel.name, "Cannot join channel (+l)") @@ -256,8 +252,6 @@ func (channel *Channel) Join(client *Client, key string) { return } - channel.membersMutex.Lock() - defer channel.membersMutex.Unlock() if channel.lists[BanMask].Match(client.nickMaskCasefolded) && !isInvited && !channel.lists[ExceptMask].Match(client.nickMaskCasefolded) { @@ -326,8 +320,8 @@ func (channel *Channel) Join(client *Client, key string) { } func (channel *Channel) Part(client *Client, message string) { - channel.membersMutex.RLock() - defer channel.membersMutex.RUnlock() + channel.membersMutex.Lock() + defer channel.membersMutex.Unlock() if !channel.members.Has(client) { client.Send(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, "You're not on that channel") @@ -644,11 +638,16 @@ func (channel *Channel) applyModeMask(client *Client, mode Mode, op ModeOp, mask return false } -func (channel *Channel) Quit(client *Client) { +// Quit removes the given client from the channel, and also updates friends with the latest client list. +func (channel *Channel) Quit(client *Client, friends *ClientSet) { channel.membersMutex.Lock() defer channel.membersMutex.Unlock() channel.quitNoMutex(client) + + for friend := range channel.members { + friends.Add(friend) + } } func (channel *Channel) quitNoMutex(client *Client) { diff --git a/irc/client.go b/irc/client.go index 619b5480..d47b4492 100644 --- a/irc/client.go +++ b/irc/client.go @@ -482,7 +482,7 @@ func (client *Client) destroy() { // clean up channels for channel := range client.channels { - channel.Quit(client) + channel.Quit(client, &friends) } // clean up server