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.
This commit is contained in:
Daniel Oaks 2017-03-26 22:11:09 +10:00
parent cec3416eae
commit 300d02bd9c
2 changed files with 12 additions and 13 deletions

View File

@ -221,11 +221,7 @@ func (channel *Channel) modeStringNoLock(client *Client) (str string) {
} }
func (channel *Channel) IsFull() bool { func (channel *Channel) IsFull() bool {
channel.membersMutex.RLock() return (channel.userLimit > 0) && (uint64(len(channel.members)) >= channel.userLimit)
defer channel.membersMutex.RUnlock()
return (channel.userLimit > 0) &&
(uint64(len(channel.members)) >= channel.userLimit)
} }
func (channel *Channel) CheckKey(key string) bool { 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) { func (channel *Channel) Join(client *Client, key string) {
channel.membersMutex.Lock() channel.membersMutex.Lock()
defer channel.membersMutex.Unlock()
if channel.members.Has(client) { if channel.members.Has(client) {
// already joined, no message? // already joined, no message needs to be sent
return return
} }
channel.membersMutex.Unlock()
if channel.IsFull() { if channel.IsFull() {
client.Send(nil, client.server.name, ERR_CHANNELISFULL, channel.name, "Cannot join channel (+l)") 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 return
} }
channel.membersMutex.Lock()
defer channel.membersMutex.Unlock()
if channel.lists[BanMask].Match(client.nickMaskCasefolded) && if channel.lists[BanMask].Match(client.nickMaskCasefolded) &&
!isInvited && !isInvited &&
!channel.lists[ExceptMask].Match(client.nickMaskCasefolded) { !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) { func (channel *Channel) Part(client *Client, message string) {
channel.membersMutex.RLock() channel.membersMutex.Lock()
defer channel.membersMutex.RUnlock() defer channel.membersMutex.Unlock()
if !channel.members.Has(client) { if !channel.members.Has(client) {
client.Send(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, "You're not on that channel") 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 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() channel.membersMutex.Lock()
defer channel.membersMutex.Unlock() defer channel.membersMutex.Unlock()
channel.quitNoMutex(client) channel.quitNoMutex(client)
for friend := range channel.members {
friends.Add(friend)
}
} }
func (channel *Channel) quitNoMutex(client *Client) { func (channel *Channel) quitNoMutex(client *Client) {

View File

@ -482,7 +482,7 @@ func (client *Client) destroy() {
// clean up channels // clean up channels
for channel := range client.channels { for channel := range client.channels {
channel.Quit(client) channel.Quit(client, &friends)
} }
// clean up server // clean up server