3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-14 07:59:31 +01:00

Merge pull request #168 from slingamn/clientmanager

refactor ClientManager
This commit is contained in:
Daniel Oaks 2017-11-23 08:35:50 +10:00 committed by GitHub
commit ad351268be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 160 additions and 220 deletions

View File

@ -80,3 +80,22 @@ To debug a hang, the best thing to do is to get a stack trace. Go's nice, and yo
$ kill -ABRT <procid> $ kill -ABRT <procid>
This will kill Oragono and print out a stack trace for you to take a look at. This will kill Oragono and print out a stack trace for you to take a look at.
## Concurrency design
Oragono involves a fair amount of shared state. Here are some of the main points:
1. Each client has a separate goroutine that listens for incoming messages and synchronously processes them.
1. All sends to clients are asynchronous; `client.Send` appends the message to a queue, which is then processed on a separate goroutine. It is always safe to call `client.Send`.
1. The server has a few of its own goroutines, for listening on sockets and handing off new client connections to their dedicated goroutines.
1. A few tasks are done asynchronously in ad-hoc goroutines.
In consequence, there is a lot of state (in particular, server and channel state) that can be read and written from multiple goroutines. This state is protected with mutexes. To avoid deadlocks, mutexes are arranged in "tiers"; while holding a mutex of one tier, you're only allowed to acquire mutexes of a strictly *higher* tier. The tiers are:
1. Tier 1 mutexes: these are the "innermost" mutexes. They typically protect getters and setters on objects, or invariants that are local to the state of a single object. Example: `Channel.stateMutex`.
1. Tier 2 mutexes: these protect some invariants of their own, but also need to access fields on other objects that themselves require synchronization. Example: `ChannelManager.RWMutex`.
1. Tier 3 mutexes: these protect macroscopic operations, where it doesn't make sense for more than one to occur concurrently. Example; `Server.rehashMutex`, which prevents rehashes from overlapping.
There are some mutexes that are "tier 0": anything in a subpackage of `irc` (e.g., `irc/logger` or `irc/connection_limits`) shouldn't acquire mutexes defined in `irc`.
We are using `buntdb` for persistence; a `buntdb.DB` has an `RWMutex` inside it, with read-write transactions getting the `Lock()` and read-only transactions getting the `RLock()`. We haven't completely decided where this lock fits into the overall lock model. For now, it's probably better to err on the side of caution: if possible, don't acquire new locks inside the `buntdb` transaction, and be careful about what locks are held around the transaction as well.

View File

@ -68,7 +68,7 @@ The `stable` branch contains the latest release. You can run this for a producti
[![Build Status](https://travis-ci.org/oragono/oragono.svg?branch=master)](https://travis-ci.org/oragono/oragono) [![Build Status](https://travis-ci.org/oragono/oragono.svg?branch=master)](https://travis-ci.org/oragono/oragono)
Clone the appropriate branch. From the root folder, run `make` to generate all release files for all of our target OSes: Clone the appropriate branch. If necessary, do `git submodule update --init` to set up vendored dependencies. From the root folder, run `make` to generate all release files for all of our target OSes:
``` ```
make make
``` ```

View File

@ -35,7 +35,7 @@ type Channel struct {
createdTime time.Time createdTime time.Time
registeredFounder string registeredFounder string
registeredTime time.Time registeredTime time.Time
stateMutex sync.RWMutex stateMutex sync.RWMutex // tier 1
topic string topic string
topicSetBy string topicSetBy string
topicSetTime time.Time topicSetTime time.Time

View File

@ -73,7 +73,7 @@ type Client struct {
saslValue string saslValue string
server *Server server *Server
socket *Socket socket *Socket
stateMutex sync.RWMutex // generic protection for mutable state stateMutex sync.RWMutex // tier 1
username string username string
vhost string vhost string
whoisLine string whoisLine string
@ -313,11 +313,15 @@ func (client *Client) IdleSeconds() uint64 {
// HasNick returns true if the client's nickname is set (used in registration). // HasNick returns true if the client's nickname is set (used in registration).
func (client *Client) HasNick() bool { func (client *Client) HasNick() bool {
client.stateMutex.RLock()
defer client.stateMutex.RUnlock()
return client.nick != "" && client.nick != "*" return client.nick != "" && client.nick != "*"
} }
// HasUsername returns true if the client's username is set (used in registration). // HasUsername returns true if the client's username is set (used in registration).
func (client *Client) HasUsername() bool { func (client *Client) HasUsername() bool {
client.stateMutex.RLock()
defer client.stateMutex.RUnlock()
return client.username != "" && client.username != "*" return client.username != "" && client.username != "*"
} }
@ -403,6 +407,7 @@ func (client *Client) updateNickMask(nick string) {
} }
client.stateMutex.Lock() client.stateMutex.Lock()
defer client.stateMutex.Unlock()
if len(client.vhost) > 0 { if len(client.vhost) > 0 {
client.hostname = client.vhost client.hostname = client.vhost
@ -419,8 +424,6 @@ func (client *Client) updateNickMask(nick string) {
client.nickMaskString = nickMaskString client.nickMaskString = nickMaskString
client.nickMaskCasefolded = nickMaskCasefolded client.nickMaskCasefolded = nickMaskCasefolded
client.stateMutex.Unlock()
} }
// AllNickmasks returns all the possible nickmasks for the client. // AllNickmasks returns all the possible nickmasks for the client.
@ -449,36 +452,6 @@ func (client *Client) AllNickmasks() []string {
return masks return masks
} }
// SetNickname sets the very first nickname for the client.
func (client *Client) SetNickname(nickname string) error {
if client.HasNick() {
client.server.logger.Error("nick", fmt.Sprintf("%s nickname already set, something is wrong with server consistency", client.nickMaskString))
return ErrNickAlreadySet
}
err := client.server.clients.Add(client, nickname)
if err == nil {
client.updateNick(nickname)
}
return err
}
// ChangeNickname changes the existing nickname of the client.
func (client *Client) ChangeNickname(nickname string) error {
origNickMask := client.nickMaskString
err := client.server.clients.Replace(client.nick, nickname, client)
if err == nil {
client.server.logger.Debug("nick", fmt.Sprintf("%s changed nickname to %s", client.nick, nickname))
client.server.snomasks.Send(sno.LocalNicks, fmt.Sprintf(ircfmt.Unescape("$%s$r changed nickname to %s"), client.nick, nickname))
client.server.whoWas.Append(client)
client.updateNickMask(nickname)
for friend := range client.Friends() {
friend.Send(nil, origNickMask, "NICK", nickname)
}
}
return err
}
// LoggedIntoAccount returns true if this client is logged into an account. // LoggedIntoAccount returns true if this client is logged into an account.
func (client *Client) LoggedIntoAccount() bool { func (client *Client) LoggedIntoAccount() bool {
return client.account != nil && client.account != &NoAccount return client.account != nil && client.account != &NoAccount

View File

@ -20,7 +20,6 @@ import (
var ( var (
ErrNickMissing = errors.New("nick missing") ErrNickMissing = errors.New("nick missing")
ErrNicknameInUse = errors.New("nickname in use") ErrNicknameInUse = errors.New("nickname in use")
ErrNicknameMismatch = errors.New("nickname mismatch")
) )
// ExpandUserHost takes a userhost, and returns an expanded version. // ExpandUserHost takes a userhost, and returns an expanded version.
@ -37,132 +36,108 @@ func ExpandUserHost(userhost string) (expanded string) {
return return
} }
// ClientLookupSet represents a way to store, search and lookup clients. // ClientManager keeps track of clients by nick, enforcing uniqueness of casefolded nicks
type ClientLookupSet struct { type ClientManager struct {
ByNickMutex sync.RWMutex sync.RWMutex // tier 2
ByNick map[string]*Client byNick map[string]*Client
} }
// NewClientLookupSet returns a new lookup set. // NewClientManager returns a new ClientManager.
func NewClientLookupSet() *ClientLookupSet { func NewClientManager() *ClientManager {
return &ClientLookupSet{ return &ClientManager{
ByNick: make(map[string]*Client), byNick: make(map[string]*Client),
} }
} }
// Count returns how many clients are in the lookup set. // Count returns how many clients are in the manager.
func (clients *ClientLookupSet) Count() int { func (clients *ClientManager) Count() int {
clients.ByNickMutex.RLock() clients.RLock()
defer clients.ByNickMutex.RUnlock() defer clients.RUnlock()
count := len(clients.ByNick) count := len(clients.byNick)
return count return count
} }
// Has returns whether or not the given client exists. // Get retrieves a client from the manager, if they exist.
//TODO(dan): This seems like ripe ground for a race, if code does Has then Get, and assumes the Get will return a client. func (clients *ClientManager) Get(nick string) *Client {
func (clients *ClientLookupSet) Has(nick string) bool {
casefoldedName, err := CasefoldName(nick) casefoldedName, err := CasefoldName(nick)
if err == nil { if err == nil {
return false clients.RLock()
} defer clients.RUnlock()
clients.ByNickMutex.RLock() cli := clients.byNick[casefoldedName]
defer clients.ByNickMutex.RUnlock()
_, exists := clients.ByNick[casefoldedName]
return exists
}
// getNoMutex is used internally, for getting clients when no mutex is required (i.e. is already set).
func (clients *ClientLookupSet) getNoMutex(nick string) *Client {
casefoldedName, err := CasefoldName(nick)
if err == nil {
cli := clients.ByNick[casefoldedName]
return cli return cli
} }
return nil return nil
} }
// Get retrieves a client from the set, if they exist. func (clients *ClientManager) removeInternal(client *Client) (removed bool) {
func (clients *ClientLookupSet) Get(nick string) *Client { // requires holding the writable Lock()
casefoldedName, err := CasefoldName(nick) oldcfnick := client.NickCasefolded()
if err == nil { currentEntry, present := clients.byNick[oldcfnick]
clients.ByNickMutex.RLock() if present {
defer clients.ByNickMutex.RUnlock() if currentEntry == client {
cli := clients.ByNick[casefoldedName] delete(clients.byNick, oldcfnick)
return cli removed = true
} else {
// this shouldn't happen, but we can ignore it
client.server.logger.Warning("internal", fmt.Sprintf("clients for nick %s out of sync", oldcfnick))
} }
return nil
} }
return
// Add adds a client to the lookup set.
func (clients *ClientLookupSet) Add(client *Client, nick string) error {
nick, err := CasefoldName(nick)
if err != nil {
return err
}
clients.ByNickMutex.Lock()
defer clients.ByNickMutex.Unlock()
if clients.getNoMutex(nick) != nil {
return ErrNicknameInUse
}
clients.ByNick[nick] = client
return nil
} }
// Remove removes a client from the lookup set. // Remove removes a client from the lookup set.
func (clients *ClientLookupSet) Remove(client *Client) error { func (clients *ClientManager) Remove(client *Client) error {
clients.Lock()
defer clients.Unlock()
if !client.HasNick() { if !client.HasNick() {
return ErrNickMissing return ErrNickMissing
} }
clients.ByNickMutex.Lock() clients.removeInternal(client)
defer clients.ByNickMutex.Unlock()
if clients.getNoMutex(client.nick) != client {
return ErrNicknameMismatch
}
delete(clients.ByNick, client.nickCasefolded)
return nil return nil
} }
// Replace renames an existing client in the lookup set. // SetNick sets a client's nickname, validating it against nicknames in use
func (clients *ClientLookupSet) Replace(oldNick, newNick string, client *Client) error { func (clients *ClientManager) SetNick(client *Client, newNick string) error {
// get casefolded nicknames newcfnick, err := CasefoldName(newNick)
oldNick, err := CasefoldName(oldNick)
if err != nil {
return err
}
newNick, err = CasefoldName(newNick)
if err != nil { if err != nil {
return err return err
} }
// remove and replace clients.Lock()
clients.ByNickMutex.Lock() defer clients.Unlock()
defer clients.ByNickMutex.Unlock()
oldClient := clients.ByNick[newNick] clients.removeInternal(client)
if oldClient == nil || oldClient == client { currentNewEntry := clients.byNick[newcfnick]
// whoo // the client may just be changing case
} else { if currentNewEntry != nil && currentNewEntry != client {
return ErrNicknameInUse return ErrNicknameInUse
} }
clients.byNick[newcfnick] = client
if oldNick == newNick { client.updateNickMask(newNick)
// if they're only changing case, don't need to remove+re-add them
return nil return nil
} }
delete(clients.ByNick, oldNick) func (clients *ClientManager) AllClients() (result []*Client) {
clients.ByNick[newNick] = client clients.RLock()
return nil defer clients.RUnlock()
result = make([]*Client, len(clients.byNick))
i := 0
for _, client := range clients.byNick {
result[i] = client
i++
}
return
} }
// AllWithCaps returns all clients with the given capabilities. // AllWithCaps returns all clients with the given capabilities.
func (clients *ClientLookupSet) AllWithCaps(capabs ...caps.Capability) (set ClientSet) { func (clients *ClientManager) AllWithCaps(capabs ...caps.Capability) (set ClientSet) {
set = make(ClientSet) set = make(ClientSet)
clients.ByNickMutex.RLock() clients.RLock()
defer clients.ByNickMutex.RUnlock() defer clients.RUnlock()
var client *Client var client *Client
for _, client = range clients.ByNick { for _, client = range clients.byNick {
// make sure they have all the required caps // make sure they have all the required caps
for _, capab := range capabs { for _, capab := range capabs {
if !client.capabilities.Has(capab) { if !client.capabilities.Has(capab) {
@ -177,7 +152,7 @@ func (clients *ClientLookupSet) AllWithCaps(capabs ...caps.Capability) (set Clie
} }
// FindAll returns all clients that match the given userhost mask. // FindAll returns all clients that match the given userhost mask.
func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) { func (clients *ClientManager) FindAll(userhost string) (set ClientSet) {
set = make(ClientSet) set = make(ClientSet)
userhost, err := Casefold(ExpandUserHost(userhost)) userhost, err := Casefold(ExpandUserHost(userhost))
@ -186,9 +161,9 @@ func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) {
} }
matcher := ircmatch.MakeMatch(userhost) matcher := ircmatch.MakeMatch(userhost)
clients.ByNickMutex.RLock() clients.RLock()
defer clients.ByNickMutex.RUnlock() defer clients.RUnlock()
for _, client := range clients.ByNick { for _, client := range clients.byNick {
if matcher.Match(client.nickMaskCasefolded) { if matcher.Match(client.nickMaskCasefolded) {
set.Add(client) set.Add(client)
} }
@ -198,7 +173,7 @@ func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) {
} }
// Find returns the first client that matches the given userhost mask. // Find returns the first client that matches the given userhost mask.
func (clients *ClientLookupSet) Find(userhost string) *Client { func (clients *ClientManager) Find(userhost string) *Client {
userhost, err := Casefold(ExpandUserHost(userhost)) userhost, err := Casefold(ExpandUserHost(userhost))
if err != nil { if err != nil {
return nil return nil
@ -206,9 +181,9 @@ func (clients *ClientLookupSet) Find(userhost string) *Client {
matcher := ircmatch.MakeMatch(userhost) matcher := ircmatch.MakeMatch(userhost)
var matchedClient *Client var matchedClient *Client
clients.ByNickMutex.RLock() clients.RLock()
defer clients.ByNickMutex.RUnlock() defer clients.RUnlock()
for _, client := range clients.ByNick { for _, client := range clients.byNick {
if matcher.Match(client.nickMaskCasefolded) { if matcher.Match(client.nickMaskCasefolded) {
matchedClient = client matchedClient = client
break break

View File

@ -82,7 +82,7 @@ type dLineNet struct {
// DLineManager manages and dlines. // DLineManager manages and dlines.
type DLineManager struct { type DLineManager struct {
sync.RWMutex sync.RWMutex // tier 1
// addresses that are dlined // addresses that are dlined
addresses map[string]*dLineAddr addresses map[string]*dLineAddr
// networks that are dlined // networks that are dlined
@ -386,8 +386,7 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
var killedClientNicks []string var killedClientNicks []string
var toKill bool var toKill bool
server.clients.ByNickMutex.RLock() for _, mcl := range server.clients.AllClients() {
for _, mcl := range server.clients.ByNick {
if hostNet == nil { if hostNet == nil {
toKill = hostAddr.Equal(mcl.IP()) toKill = hostAddr.Equal(mcl.IP())
} else { } else {
@ -399,7 +398,6 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
killedClientNicks = append(killedClientNicks, mcl.nick) killedClientNicks = append(killedClientNicks, mcl.nick)
} }
} }
server.clients.ByNickMutex.RUnlock()
for _, mcl := range clientsToKill { for _, mcl := range clientsToKill {
mcl.exitedSnomaskSent = true mcl.exitedSnomaskSent = true

View File

@ -29,7 +29,7 @@ const (
) )
type IdleTimer struct { type IdleTimer struct {
sync.Mutex sync.Mutex // tier 1
// immutable after construction // immutable after construction
registerTimeout time.Duration registerTimeout time.Duration

View File

@ -35,7 +35,7 @@ type KLineInfo struct {
// KLineManager manages and klines. // KLineManager manages and klines.
type KLineManager struct { type KLineManager struct {
sync.RWMutex sync.RWMutex // tier 1
// kline'd entries // kline'd entries
entries map[string]*KLineInfo entries map[string]*KLineInfo
} }
@ -282,8 +282,7 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
var clientsToKill []*Client var clientsToKill []*Client
var killedClientNicks []string var killedClientNicks []string
server.clients.ByNickMutex.RLock() for _, mcl := range server.clients.AllClients() {
for _, mcl := range server.clients.ByNick {
for _, clientMask := range mcl.AllNickmasks() { for _, clientMask := range mcl.AllNickmasks() {
if matcher.Match(clientMask) { if matcher.Match(clientMask) {
clientsToKill = append(clientsToKill, mcl) clientsToKill = append(clientsToKill, mcl)
@ -291,7 +290,6 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
} }
} }
server.clients.ByNickMutex.RUnlock()
for _, mcl := range clientsToKill { for _, mcl := range clientsToKill {
mcl.exitedSnomaskSent = true mcl.exitedSnomaskSent = true

View File

@ -15,7 +15,7 @@ import (
// MonitorManager keeps track of who's monitoring which nicks. // MonitorManager keeps track of who's monitoring which nicks.
type MonitorManager struct { type MonitorManager struct {
sync.RWMutex sync.RWMutex // tier 2
// client -> nicks it's watching // client -> nicks it's watching
watching map[*Client]map[string]bool watching map[*Client]map[string]bool
// nick -> clients watching it // nick -> clients watching it

View File

@ -8,7 +8,9 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/goshuirc/irc-go/ircfmt"
"github.com/goshuirc/irc-go/ircmsg" "github.com/goshuirc/irc-go/ircmsg"
"github.com/oragono/oragono/irc/sno"
) )
var ( var (
@ -26,79 +28,63 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
return true return true
} }
nicknameRaw := strings.TrimSpace(msg.Params[0]) return performNickChange(server, client, client, msg.Params[0])
nickname, err := CasefoldName(nicknameRaw)
if len(nicknameRaw) < 1 {
client.Send(nil, server.name, ERR_NONICKNAMEGIVEN, client.nick, "No nickname given")
return false
} }
if err != nil || len(nicknameRaw) > server.Limits().NickLen || restrictedNicknames[nickname] { func performNickChange(server *Server, client *Client, target *Client, newnick string) bool {
client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, nicknameRaw, "Erroneous nickname") nickname := strings.TrimSpace(newnick)
return false cfnick, err := CasefoldName(nickname)
}
if client.nick == nickname {
return false
}
// bleh, this will be replaced and done below
if client.registered {
err = client.ChangeNickname(nicknameRaw)
} else {
err = client.SetNickname(nicknameRaw)
}
if err == ErrNicknameInUse {
client.Send(nil, server.name, ERR_NICKNAMEINUSE, client.nick, nicknameRaw, "Nickname is already in use")
return false
} else if err != nil {
client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "NICK", fmt.Sprintf("Could not set or change nickname: %s", err.Error()))
return false
}
if client.registered {
client.server.monitorManager.AlertAbout(client, true)
}
server.tryRegister(client)
return false
}
// SANICK <oldnick> <nickname>
func sanickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
if !client.authorized {
client.Quit("Bad password")
return true
}
oldnick, oerr := CasefoldName(msg.Params[0])
nickname, err := CasefoldName(msg.Params[1])
if len(nickname) < 1 { if len(nickname) < 1 {
client.Send(nil, server.name, ERR_NONICKNAMEGIVEN, client.nick, "No nickname given") client.Send(nil, server.name, ERR_NONICKNAMEGIVEN, client.nick, "No nickname given")
return false return false
} }
if oerr != nil || err != nil || len(strings.TrimSpace(msg.Params[1])) > server.limits.NickLen || restrictedNicknames[nickname] { if err != nil || len(nickname) > server.Limits().NickLen || restrictedNicknames[cfnick] {
client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, msg.Params[0], "Erroneous nickname") client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, nickname, "Erroneous nickname")
return false return false
} }
if client.nick == msg.Params[1] { if target.Nick() == nickname {
return false return false
} }
target := server.clients.Get(oldnick) hadNick := target.HasNick()
origNick := target.Nick()
origNickMask := target.NickMaskString()
err = client.server.clients.SetNick(target, nickname)
if err == ErrNicknameInUse {
client.Send(nil, server.name, ERR_NICKNAMEINUSE, client.nick, nickname, "Nickname is already in use")
return false
} else if err != nil {
client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "NICK", fmt.Sprintf("Could not set or change nickname: %s", err.Error()))
return false
}
client.server.logger.Debug("nick", fmt.Sprintf("%s changed nickname to %s [%s]", origNickMask, nickname, cfnick))
if hadNick {
target.server.snomasks.Send(sno.LocalNicks, fmt.Sprintf(ircfmt.Unescape("$%s$r changed nickname to %s"), origNick, nickname))
target.server.whoWas.Append(client)
for friend := range target.Friends() {
friend.Send(nil, origNickMask, "NICK", nickname)
}
}
if target.registered {
client.server.monitorManager.AlertAbout(target, true)
} else {
server.tryRegister(target)
}
return false
}
// SANICK <oldnick> <nickname>
func sanickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
targetNick := strings.TrimSpace(msg.Params[0])
target := server.clients.Get(targetNick)
if target == nil { if target == nil {
client.Send(nil, server.name, ERR_NOSUCHNICK, client.nick, msg.Params[0], "No such nick") client.Send(nil, server.name, ERR_NOSUCHNICK, client.nick, msg.Params[0], "No such nick")
return false return false
} }
return performNickChange(server, client, target, msg.Params[1])
//TODO(dan): There's probably some races here, we should be changing this in the primary server thread
if server.clients.Get(nickname) != nil && server.clients.Get(nickname) != target {
client.Send(nil, server.name, ERR_NICKNAMEINUSE, client.nick, msg.Params[0], "Nickname is already in use")
return false
}
target.ChangeNickname(msg.Params[1])
return false
} }

View File

@ -74,7 +74,7 @@ type ListenerWrapper struct {
// lets the ListenerWrapper inform the server that it has stopped: // lets the ListenerWrapper inform the server that it has stopped:
stopEvent chan bool stopEvent chan bool
// protects atomic update of tlsConfig and shouldStop: // protects atomic update of tlsConfig and shouldStop:
configMutex sync.Mutex configMutex sync.Mutex // tier 1
} }
// Server is the main Oragono server. // Server is the main Oragono server.
@ -86,10 +86,10 @@ type Server struct {
channels *ChannelManager channels *ChannelManager
channelRegistry *ChannelRegistry channelRegistry *ChannelRegistry
checkIdent bool checkIdent bool
clients *ClientLookupSet clients *ClientManager
commands chan Command commands chan Command
configFilename string configFilename string
configurableStateMutex sync.RWMutex // generic protection for server state modified by rehash() configurableStateMutex sync.RWMutex // tier 1; generic protection for server state modified by rehash()
connectionLimiter *connection_limits.Limiter connectionLimiter *connection_limits.Limiter
connectionThrottler *connection_limits.Throttler connectionThrottler *connection_limits.Throttler
ctime time.Time ctime time.Time
@ -113,7 +113,7 @@ type Server struct {
password []byte password []byte
passwords *passwd.SaltedManager passwords *passwd.SaltedManager
recoverFromErrors bool recoverFromErrors bool
rehashMutex sync.Mutex rehashMutex sync.Mutex // tier 3
rehashSignal chan os.Signal rehashSignal chan os.Signal
proxyAllowedFrom []string proxyAllowedFrom []string
signals chan os.Signal signals chan os.Signal
@ -149,7 +149,7 @@ func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
server := &Server{ server := &Server{
accounts: make(map[string]*ClientAccount), accounts: make(map[string]*ClientAccount),
channels: NewChannelManager(), channels: NewChannelManager(),
clients: NewClientLookupSet(), clients: NewClientManager(),
commands: make(chan Command), commands: make(chan Command),
connectionLimiter: connection_limits.NewLimiter(), connectionLimiter: connection_limits.NewLimiter(),
connectionThrottler: connection_limits.NewThrottler(), connectionThrottler: connection_limits.NewThrottler(),
@ -238,11 +238,9 @@ func loadChannelList(channel *Channel, list string, maskMode Mode) {
// Shutdown shuts down the server. // Shutdown shuts down the server.
func (server *Server) Shutdown() { func (server *Server) Shutdown() {
//TODO(dan): Make sure we disallow new nicks //TODO(dan): Make sure we disallow new nicks
server.clients.ByNickMutex.RLock() for _, client := range server.clients.AllClients() {
for _, client := range server.clients.ByNick {
client.Notice("Server is shutting down") client.Notice("Server is shutting down")
} }
server.clients.ByNickMutex.RUnlock()
if err := server.store.Close(); err != nil { if err := server.store.Close(); err != nil {
server.logger.Error("shutdown", fmt.Sprintln("Could not close datastore:", err)) server.logger.Error("shutdown", fmt.Sprintln("Could not close datastore:", err))
@ -1332,11 +1330,9 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
server.configurableStateMutex.Unlock() server.configurableStateMutex.Unlock()
// update on all clients // update on all clients
server.clients.ByNickMutex.RLock() for _, sClient := range server.clients.AllClients() {
for _, sClient := range server.clients.ByNick {
sClient.socket.MaxSendQBytes = config.Server.MaxSendQBytes sClient.socket.MaxSendQBytes = config.Server.MaxSendQBytes
} }
server.clients.ByNickMutex.RUnlock()
} }
// set RPL_ISUPPORT // set RPL_ISUPPORT
@ -1370,8 +1366,7 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
if !initial { if !initial {
// push new info to all of our clients // push new info to all of our clients
server.clients.ByNickMutex.RLock() for _, sClient := range server.clients.AllClients() {
for _, sClient := range server.clients.ByNick {
for _, tokenline := range newISupportReplies { for _, tokenline := range newISupportReplies {
sClient.Send(nil, server.name, RPL_ISUPPORT, append([]string{sClient.nick}, tokenline...)...) sClient.Send(nil, server.name, RPL_ISUPPORT, append([]string{sClient.nick}, tokenline...)...)
} }
@ -1380,7 +1375,6 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
sClient.Notice(rawIONotice) sClient.Notice(rawIONotice)
} }
} }
server.clients.ByNickMutex.RUnlock()
} }
return nil return nil
@ -2008,10 +2002,7 @@ func lusersHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
//TODO(vegax87) Fix network statistics and additional parameters //TODO(vegax87) Fix network statistics and additional parameters
var totalcount, invisiblecount, opercount int var totalcount, invisiblecount, opercount int
server.clients.ByNickMutex.RLock() for _, onlineusers := range server.clients.AllClients() {
defer server.clients.ByNickMutex.RUnlock()
for _, onlineusers := range server.clients.ByNick {
totalcount++ totalcount++
if onlineusers.flags[Invisible] { if onlineusers.flags[Invisible] {
invisiblecount++ invisiblecount++

View File

@ -10,7 +10,7 @@ import (
// SnoManager keeps track of which clients to send snomasks to. // SnoManager keeps track of which clients to send snomasks to.
type SnoManager struct { type SnoManager struct {
sendListMutex sync.RWMutex sendListMutex sync.RWMutex // tier 2
sendLists map[sno.Mask]map[*Client]bool sendLists map[sno.Mask]map[*Client]bool
} }

View File

@ -14,7 +14,7 @@ type WhoWasList struct {
start int start int
end int end int
accessMutex sync.RWMutex accessMutex sync.RWMutex // tier 2
} }
// WhoWas is an entry in the WhoWasList. // WhoWas is an entry in the WhoWasList.