remove channelJoinPartMutex

This commit is contained in:
Shivaram Lingamneni 2017-10-30 05:21:47 -04:00
parent d715abf0f0
commit 94cf438f51
7 changed files with 260 additions and 162 deletions

View File

@ -7,7 +7,6 @@ package irc
import ( import (
"fmt" "fmt"
"log"
"strconv" "strconv"
"time" "time"
@ -41,7 +40,7 @@ type Channel struct {
func NewChannel(s *Server, name string, addDefaultModes bool) *Channel { func NewChannel(s *Server, name string, addDefaultModes bool) *Channel {
casefoldedName, err := CasefoldChannel(name) casefoldedName, err := CasefoldChannel(name)
if err != nil { if err != nil {
log.Println(fmt.Sprintf("ERROR: Channel name is bad: [%s]", name), err.Error()) s.logger.Error("internal", fmt.Sprintf("Bad channel name %s: %v", name, err))
return nil return nil
} }
@ -59,13 +58,11 @@ func NewChannel(s *Server, name string, addDefaultModes bool) *Channel {
} }
if addDefaultModes { if addDefaultModes {
for _, mode := range s.GetDefaultChannelModes() { for _, mode := range s.DefaultChannelModes() {
channel.flags[mode] = true channel.flags[mode] = true
} }
} }
s.channels.Add(channel)
return channel return channel
} }
@ -281,6 +278,12 @@ func (channel *Channel) CheckKey(key string) bool {
return (channel.key == "") || (channel.key == key) return (channel.key == "") || (channel.key == key)
} }
func (channel *Channel) IsEmpty() bool {
channel.stateMutex.RLock()
defer channel.stateMutex.RUnlock()
return len(channel.members) == 0
}
// Join joins the given client to this channel (if they can be joined). // Join joins the given client to this channel (if they can be joined).
//TODO(dan): /SAJOIN and maybe a ForceJoin function? //TODO(dan): /SAJOIN and maybe a ForceJoin function?
func (channel *Channel) Join(client *Client, key string) { func (channel *Channel) Join(client *Client, key string) {
@ -684,16 +687,10 @@ func (channel *Channel) applyModeMask(client *Client, mode Mode, op ModeOp, mask
func (channel *Channel) Quit(client *Client) { func (channel *Channel) Quit(client *Client) {
channel.stateMutex.Lock() channel.stateMutex.Lock()
channel.members.Remove(client) channel.members.Remove(client)
empty := len(channel.members) == 0
channel.stateMutex.Unlock() channel.stateMutex.Unlock()
channel.regenerateMembersCache() channel.regenerateMembersCache()
client.removeChannel(channel) client.removeChannel(channel)
//TODO(slingamn) fold this operation into a channelmanager type
if empty {
channel.server.channels.Remove(channel)
}
} }
func (channel *Channel) Kick(client *Client, target *Client, comment string) { func (channel *Channel) Kick(client *Client, target *Client, comment string) {

162
irc/channelmanager.go Normal file
View File

@ -0,0 +1,162 @@
// Copyright (c) 2017 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package irc
import (
"errors"
"sync"
)
var (
InvalidChannelName = errors.New("Invalid channel name")
NoSuchChannel = errors.New("No such channel")
ChannelNameInUse = errors.New("Channel name in use")
)
type channelManagerEntry struct {
channel *Channel
// this is a refcount for joins, so we can avoid a race where we incorrectly
// think the channel is empty (without holding a lock across the entire Channel.Join()
// call)
pendingJoins int
}
// ChannelManager keeps track of all the channels on the server,
// providing synchronization for creation of new channels on first join,
// cleanup of empty channels on last part, and renames.
type ChannelManager struct {
sync.RWMutex // tier 2
chans map[string]*channelManagerEntry
}
// NewChannelManager returns a new ChannelManager.
func NewChannelManager() *ChannelManager {
return &ChannelManager{
chans: make(map[string]*channelManagerEntry),
}
}
// Get returns an existing channel with name equivalent to `name`, or nil
func (cm *ChannelManager) Get(name string) *Channel {
name, err := CasefoldChannel(name)
if err == nil {
cm.RLock()
defer cm.RUnlock()
return cm.chans[name].channel
}
return nil
}
// Join causes `client` to join the channel named `name`, creating it if necessary.
func (cm *ChannelManager) Join(client *Client, name string, key string) error {
server := client.server
casefoldedName, err := CasefoldChannel(name)
if err != nil || len(casefoldedName) > server.getLimits().ChannelLen {
return NoSuchChannel
}
cm.Lock()
entry := cm.chans[casefoldedName]
if entry == nil {
entry = &channelManagerEntry{
channel: NewChannel(server, name, true),
pendingJoins: 0,
}
cm.chans[casefoldedName] = entry
}
entry.pendingJoins += 1
cm.Unlock()
entry.channel.Join(client, key)
cm.maybeCleanup(entry, true)
return nil
}
func (cm *ChannelManager) maybeCleanup(entry *channelManagerEntry, afterJoin bool) {
cm.Lock()
defer cm.Unlock()
if entry.channel == nil {
return
}
if afterJoin {
entry.pendingJoins -= 1
}
if entry.channel.IsEmpty() && entry.pendingJoins == 0 {
// reread the name, handling the case where the channel was renamed
casefoldedName := entry.channel.NameCasefolded()
delete(cm.chans, casefoldedName)
// invalidate the entry (otherwise, a subsequent cleanup attempt could delete
// a valid, distinct entry under casefoldedName):
entry.channel = nil
}
}
// Part parts `client` from the channel named `name`, deleting it if it's empty.
func (cm *ChannelManager) Part(client *Client, name string, message string) error {
casefoldedName, err := CasefoldChannel(name)
if err != nil {
return NoSuchChannel
}
cm.RLock()
entry := cm.chans[casefoldedName]
cm.RUnlock()
if entry == nil {
return NoSuchChannel
}
entry.channel.Part(client, message)
cm.maybeCleanup(entry, false)
return nil
}
// Rename renames a channel (but does not notify the members)
func (cm *ChannelManager) Rename(name string, newname string) error {
cfname, err := CasefoldChannel(name)
if err != nil {
return NoSuchChannel
}
cfnewname, err := CasefoldChannel(newname)
if err != nil {
return InvalidChannelName
}
cm.Lock()
defer cm.Unlock()
if cm.chans[cfnewname] != nil {
return ChannelNameInUse
}
entry := cm.chans[cfname]
if entry == nil {
return NoSuchChannel
}
delete(cm.chans, cfname)
cm.chans[cfnewname] = entry
entry.channel.setName(newname)
entry.channel.setNameCasefolded(cfnewname)
return nil
}
// Len returns the number of channels
func (cm *ChannelManager) Len() int {
cm.RLock()
defer cm.RUnlock()
return len(cm.chans)
}
// Channels returns a slice containing all current channels
func (cm *ChannelManager) Channels() (result []*Channel) {
cm.RLock()
defer cm.RUnlock()
for _, entry := range cm.chans {
result = append(result, entry.channel)
}
return
}

View File

@ -548,14 +548,12 @@ func (client *Client) destroy() {
client.server.monitorManager.RemoveAll(client) client.server.monitorManager.RemoveAll(client)
// clean up channels // clean up channels
client.server.channelJoinPartMutex.Lock() for _, channel := range client.Channels() {
for channel := range client.channels {
channel.Quit(client) channel.Quit(client)
for _, member := range channel.Members() { for _, member := range channel.Members() {
friends.Add(member) friends.Add(member)
} }
} }
client.server.channelJoinPartMutex.Unlock()
// clean up server // clean up server
client.server.clients.Remove(client) client.server.clients.Remove(client)

View File

@ -41,6 +41,12 @@ func (server *Server) WebIRCConfig() []webircConfig {
return server.webirc return server.webirc
} }
func (server *Server) DefaultChannelModes() Modes {
server.configurableStateMutex.RLock()
defer server.configurableStateMutex.RUnlock()
return server.defaultChannelModes
}
func (client *Client) getNick() string { func (client *Client) getNick() string {
client.stateMutex.RLock() client.stateMutex.RLock()
defer client.stateMutex.RUnlock() defer client.stateMutex.RUnlock()
@ -114,6 +120,24 @@ func (channel *Channel) Name() string {
return channel.name return channel.name
} }
func (channel *Channel) setName(name string) {
channel.stateMutex.Lock()
defer channel.stateMutex.Unlock()
channel.name = name
}
func (channel *Channel) NameCasefolded() string {
channel.stateMutex.RLock()
defer channel.stateMutex.RUnlock()
return channel.nameCasefolded
}
func (channel *Channel) setNameCasefolded(nameCasefolded string) {
channel.stateMutex.Lock()
defer channel.stateMutex.Unlock()
channel.nameCasefolded = nameCasefolded
}
func (channel *Channel) Members() (result []*Client) { func (channel *Channel) Members() (result []*Client) {
channel.stateMutex.RLock() channel.stateMutex.RLock()
defer channel.stateMutex.RUnlock() defer channel.stateMutex.RUnlock()

View File

@ -52,12 +52,9 @@ func (manager *MonitorManager) AlertAbout(client *Client, online bool) {
command = RPL_MONONLINE command = RPL_MONONLINE
} }
// asynchronously send all the notifications for _, mClient := range watchers {
go func() { mClient.Send(nil, client.server.name, command, mClient.getNick(), nick)
for _, mClient := range watchers { }
mClient.Send(nil, client.server.name, command, mClient.getNick(), nick)
}
}()
} }
// Add registers `client` to receive notifications about `nick`. // Add registers `client` to receive notifications about `nick`.

View File

@ -9,6 +9,7 @@ import (
"bufio" "bufio"
"crypto/tls" "crypto/tls"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"log" "log"
"math/rand" "math/rand"
@ -39,6 +40,8 @@ var (
// common error responses // common error responses
couldNotParseIPMsg, _ = (&[]ircmsg.IrcMessage{ircmsg.MakeMessage(nil, "", "ERROR", "Unable to parse your IP address")}[0]).Line() couldNotParseIPMsg, _ = (&[]ircmsg.IrcMessage{ircmsg.MakeMessage(nil, "", "ERROR", "Unable to parse your IP address")}[0]).Line()
RenamePrivsNeeded = errors.New("Only chanops can rename channels")
) )
const ( const (
@ -80,8 +83,7 @@ type Server struct {
accountRegistration *AccountRegistration accountRegistration *AccountRegistration
accounts map[string]*ClientAccount accounts map[string]*ClientAccount
channelRegistrationEnabled bool channelRegistrationEnabled bool
channels ChannelNameMap channels *ChannelManager
channelJoinPartMutex sync.Mutex // used when joining/parting channels to prevent stomping over each others' access and all
checkIdent bool checkIdent bool
clients *ClientLookupSet clients *ClientLookupSet
commands chan Command commands chan Command
@ -147,7 +149,7 @@ func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
// initialize data structures // initialize data structures
server := &Server{ server := &Server{
accounts: make(map[string]*ClientAccount), accounts: make(map[string]*ClientAccount),
channels: *NewChannelNameMap(), channels: NewChannelManager(),
clients: NewClientLookupSet(), clients: NewClientLookupSet(),
commands: make(chan Command), commands: make(chan Command),
connectionLimiter: connection_limits.NewLimiter(), connectionLimiter: connection_limits.NewLimiter(),
@ -553,53 +555,62 @@ func pongHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
// RENAME <oldchan> <newchan> [<reason>] // RENAME <oldchan> <newchan> [<reason>]
//TODO(dan): Clean up this function so it doesn't look like an eldrich horror... prolly by putting it into a server.renameChannel function. func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage) (result bool) {
func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { result = false
// get lots of locks... make sure nobody touches anything while we're doing this
// TODO(slingamn, #152) clean up locking here
server.registeredChannelsMutex.Lock() server.registeredChannelsMutex.Lock()
defer server.registeredChannelsMutex.Unlock() defer server.registeredChannelsMutex.Unlock()
server.channels.ChansLock.Lock()
defer server.channels.ChansLock.Unlock() errorResponse := func(err error, name string) {
// TODO: send correct error codes, e.g., ERR_CANNOTRENAME, ERR_CHANNAMEINUSE
var code string
switch err {
case NoSuchChannel:
code = ERR_NOSUCHCHANNEL
case RenamePrivsNeeded:
code = ERR_CHANOPRIVSNEEDED
case InvalidChannelName:
code = ERR_UNKNOWNERROR
case ChannelNameInUse:
code = ERR_UNKNOWNERROR
default:
code = ERR_UNKNOWNERROR
}
client.Send(nil, server.name, code, client.getNick(), "RENAME", name, err.Error())
}
oldName := strings.TrimSpace(msg.Params[0]) oldName := strings.TrimSpace(msg.Params[0])
newName := strings.TrimSpace(msg.Params[1]) newName := strings.TrimSpace(msg.Params[1])
if oldName == "" || newName == "" {
errorResponse(InvalidChannelName, "<empty>")
return
}
casefoldedOldName, err := CasefoldChannel(oldName)
if err != nil {
errorResponse(InvalidChannelName, oldName)
return
}
casefoldedNewName, err := CasefoldChannel(newName)
if err != nil {
errorResponse(InvalidChannelName, newName)
return
}
reason := "No reason" reason := "No reason"
if 2 < len(msg.Params) { if 2 < len(msg.Params) {
reason = msg.Params[2] reason = msg.Params[2]
} }
// check for all the reasons why the rename couldn't happen channel := server.channels.Get(oldName)
casefoldedOldName, err := CasefoldChannel(oldName)
if err != nil {
//TODO(dan): Change this to ERR_CANNOTRENAME
client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "RENAME", oldName, "Old channel name is invalid")
return false
}
channel := server.channels.Chans[casefoldedOldName]
if channel == nil { if channel == nil {
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, oldName, "No such channel") errorResponse(NoSuchChannel, oldName)
return false return
} }
//TODO(dan): allow IRCops to do this? //TODO(dan): allow IRCops to do this?
if !channel.ClientIsAtLeast(client, Operator) { if !channel.ClientIsAtLeast(client, Operator) {
client.Send(nil, server.name, ERR_CHANOPRIVSNEEDED, client.nick, oldName, "Only chanops can rename channels") errorResponse(RenamePrivsNeeded, oldName)
return false return
}
casefoldedNewName, err := CasefoldChannel(newName)
if err != nil {
//TODO(dan): Change this to ERR_CANNOTRENAME
client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "RENAME", newName, "New channel name is invalid")
return false
}
newChannel := server.channels.Chans[casefoldedNewName]
if newChannel != nil {
//TODO(dan): Change this to ERR_CHANNAMEINUSE
client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "RENAME", newName, "New channel name is in use")
return false
} }
var canEdit bool var canEdit bool
@ -622,11 +633,11 @@ func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
// perform the channel rename // perform the channel rename
server.channels.Chans[casefoldedOldName] = nil err = server.channels.Rename(oldName, newName)
server.channels.Chans[casefoldedNewName] = channel if err != nil {
errorResponse(err, newName)
channel.name = strings.TrimSpace(msg.Params[1]) return
channel.nameCasefolded = casefoldedNewName }
// rename stored channel info if any exists // rename stored channel info if any exists
server.store.Update(func(tx *buntdb.Tx) error { server.store.Update(func(tx *buntdb.Tx) error {
@ -679,34 +690,15 @@ func joinHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
keys = strings.Split(msg.Params[1], ",") keys = strings.Split(msg.Params[1], ",")
} }
// get lock
server.channelJoinPartMutex.Lock()
defer server.channelJoinPartMutex.Unlock()
for i, name := range channels { for i, name := range channels {
casefoldedName, err := CasefoldChannel(name)
if err != nil {
if len(name) > 0 {
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, name, "No such channel")
}
continue
}
channel := server.channels.Get(casefoldedName)
if channel == nil {
if len(casefoldedName) > server.getLimits().ChannelLen {
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, name, "No such channel")
continue
}
channel = NewChannel(server, name, true)
}
var key string var key string
if len(keys) > i { if len(keys) > i {
key = keys[i] key = keys[i]
} }
err := server.channels.Join(client, name, key)
channel.Join(client, key) if err == NoSuchChannel {
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.getNick(), name, "No such channel")
}
} }
return false return false
} }
@ -719,22 +711,11 @@ func partHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
reason = msg.Params[1] reason = msg.Params[1]
} }
// get lock
server.channelJoinPartMutex.Lock()
defer server.channelJoinPartMutex.Unlock()
for _, chname := range channels { for _, chname := range channels {
casefoldedChannelName, err := CasefoldChannel(chname) err := server.channels.Part(client, chname, reason)
channel := server.channels.Get(casefoldedChannelName) if err == NoSuchChannel {
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, chname, "No such channel")
if err != nil || channel == nil {
if len(chname) > 0 {
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, chname, "No such channel")
}
continue
} }
channel.Part(client, reason)
} }
return false return false
} }
@ -1096,11 +1077,9 @@ func whoHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
//} //}
if mask == "" { if mask == "" {
server.channels.ChansLock.RLock() for _, channel := range server.channels.Channels() {
for _, channel := range server.channels.Chans {
whoChannel(client, channel, friends) whoChannel(client, channel, friends)
} }
server.channels.ChansLock.RUnlock()
} else if mask[0] == '#' { } else if mask[0] == '#' {
// TODO implement wildcard matching // TODO implement wildcard matching
//TODO(dan): ^ only for opers //TODO(dan): ^ only for opers
@ -1859,8 +1838,7 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
if len(channels) == 0 { if len(channels) == 0 {
server.channels.ChansLock.RLock() for _, channel := range server.channels.Channels() {
for _, channel := range server.channels.Chans {
if !client.flags[Operator] && channel.flags[Secret] { if !client.flags[Operator] && channel.flags[Secret] {
continue continue
} }
@ -1868,7 +1846,6 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
client.RplList(channel) client.RplList(channel)
} }
} }
server.channels.ChansLock.RUnlock()
} else { } else {
// limit regular users to only listing one channel // limit regular users to only listing one channel
if !client.flags[Operator] { if !client.flags[Operator] {
@ -1922,11 +1899,9 @@ func namesHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
//} //}
if len(channels) == 0 { if len(channels) == 0 {
server.channels.ChansLock.RLock() for _, channel := range server.channels.Channels() {
for _, channel := range server.channels.Chans {
channel.Names(client) channel.Names(client)
} }
server.channels.ChansLock.RUnlock()
return false return false
} }

View File

@ -6,64 +6,9 @@
package irc package irc
import ( import (
"fmt"
"strings" "strings"
"sync"
) )
// ChannelNameMap is a map that converts channel names to actual channel objects.
type ChannelNameMap struct {
ChansLock sync.RWMutex
Chans map[string]*Channel
}
// NewChannelNameMap returns a new ChannelNameMap.
func NewChannelNameMap() *ChannelNameMap {
var channels ChannelNameMap
channels.Chans = make(map[string]*Channel)
return &channels
}
// Get returns the given channel if it exists.
func (channels *ChannelNameMap) Get(name string) *Channel {
name, err := CasefoldChannel(name)
if err == nil {
channels.ChansLock.RLock()
defer channels.ChansLock.RUnlock()
return channels.Chans[name]
}
return nil
}
// Add adds the given channel to our map.
func (channels *ChannelNameMap) Add(channel *Channel) error {
channels.ChansLock.Lock()
defer channels.ChansLock.Unlock()
if channels.Chans[channel.nameCasefolded] != nil {
return fmt.Errorf("%s: already set", channel.name)
}
channels.Chans[channel.nameCasefolded] = channel
return nil
}
// Remove removes the given channel from our map.
func (channels *ChannelNameMap) Remove(channel *Channel) error {
channels.ChansLock.Lock()
defer channels.ChansLock.Unlock()
if channel != channels.Chans[channel.nameCasefolded] {
return fmt.Errorf("%s: mismatch", channel.name)
}
delete(channels.Chans, channel.nameCasefolded)
return nil
}
// Len returns how many channels we have.
func (channels *ChannelNameMap) Len() int {
channels.ChansLock.RLock()
defer channels.ChansLock.RUnlock()
return len(channels.Chans)
}
// ModeSet holds a set of modes. // ModeSet holds a set of modes.
type ModeSet map[Mode]bool type ModeSet map[Mode]bool