tweaks to NAMES implementation (#2058)

* tweaks to NAMES implementation

* tweak member caching

* add a benchmark for NAMES
This commit is contained in:
Shivaram Lingamneni 2023-04-13 23:15:56 -07:00 committed by GitHub
parent 378d88fee2
commit eeec481b8d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 148 additions and 56 deletions

View file

@ -34,7 +34,6 @@ type Channel struct {
key string
forward string
members MemberSet
membersCache []*Client // allow iteration over channel members without holding the lock
name string
nameCasefolded string
server *Server
@ -54,6 +53,9 @@ type Channel struct {
dirtyBits uint
settings ChannelSettings
uuid utils.UUID
// these caches are paired to allow iteration over channel members without holding the lock
membersCache []*Client
memberDataCache []*memberData
}
// NewChannel creates a new channel from a `Server` and a `name`
@ -421,16 +423,19 @@ func (channel *Channel) AcceptTransfer(client *Client) (err error) {
func (channel *Channel) regenerateMembersCache() {
channel.stateMutex.RLock()
result := make([]*Client, len(channel.members))
membersCache := make([]*Client, len(channel.members))
dataCache := make([]*memberData, len(channel.members))
i := 0
for client := range channel.members {
result[i] = client
for client, info := range channel.members {
membersCache[i] = client
dataCache[i] = info
i++
}
channel.stateMutex.RUnlock()
channel.stateMutex.Lock()
channel.membersCache = result
channel.membersCache = membersCache
channel.memberDataCache = dataCache
channel.stateMutex.Unlock()
}
@ -438,6 +443,8 @@ func (channel *Channel) regenerateMembersCache() {
func (channel *Channel) Names(client *Client, rb *ResponseBuffer) {
channel.stateMutex.RLock()
clientData, isJoined := channel.members[client]
chname := channel.name
membersCache, memberDataCache := channel.membersCache, channel.memberDataCache
channel.stateMutex.RUnlock()
isOper := client.HasRoleCapabs("sajoin")
respectAuditorium := channel.flags.HasMode(modes.Auditorium) && !isOper &&
@ -445,52 +452,32 @@ func (channel *Channel) Names(client *Client, rb *ResponseBuffer) {
isMultiPrefix := rb.session.capabilities.Has(caps.MultiPrefix)
isUserhostInNames := rb.session.capabilities.Has(caps.UserhostInNames)
maxNamLen := 480 - len(client.server.name) - len(client.Nick())
var namesLines []string
var buffer strings.Builder
maxNamLen := 480 - len(client.server.name) - len(client.Nick()) - len(chname)
var tl utils.TokenLineBuilder
tl.Initialize(maxNamLen, " ")
if isJoined || !channel.flags.HasMode(modes.Secret) || isOper {
for _, target := range channel.Members() {
for i, target := range membersCache {
if !isJoined && target.HasMode(modes.Invisible) && !isOper {
continue
}
var nick string
if isUserhostInNames {
nick = target.NickMaskString()
} else {
nick = target.Nick()
}
channel.stateMutex.RLock()
memberData, _ := channel.members[target]
channel.stateMutex.RUnlock()
modeSet := memberData.modes
if modeSet == nil {
memberData := memberDataCache[i]
if respectAuditorium && memberData.modes.HighestChannelUserMode() == modes.Mode(0) {
continue
}
if !isJoined && target.HasMode(modes.Invisible) && !isOper {
continue
}
if respectAuditorium && modeSet.HighestChannelUserMode() == modes.Mode(0) {
continue
}
prefix := modeSet.Prefixes(isMultiPrefix)
if buffer.Len()+len(nick)+len(prefix)+1 > maxNamLen {
namesLines = append(namesLines, buffer.String())
buffer.Reset()
}
if buffer.Len() > 0 {
buffer.WriteString(" ")
}
buffer.WriteString(prefix)
buffer.WriteString(nick)
}
if buffer.Len() > 0 {
namesLines = append(namesLines, buffer.String())
tl.AddParts(memberData.modes.Prefixes(isMultiPrefix), nick)
}
}
for _, line := range namesLines {
if buffer.Len() > 0 {
rb.Add(nil, client.server.name, RPL_NAMREPLY, client.nick, "=", channel.name, line)
}
for _, line := range tl.Lines() {
rb.Add(nil, client.server.name, RPL_NAMREPLY, client.nick, "=", chname, line)
}
rb.Add(nil, client.server.name, RPL_ENDOFNAMES, client.nick, channel.name, client.t("End of NAMES list"))
rb.Add(nil, client.server.name, RPL_ENDOFNAMES, client.nick, chname, client.t("End of NAMES list"))
}
// does `clientMode` give you privileges to grant/remove `targetMode` to/from people,
@ -514,7 +501,7 @@ func channelUserModeHasPrivsOver(clientMode modes.Mode, targetMode modes.Mode) b
// ClientIsAtLeast returns whether the client has at least the given channel privilege.
func (channel *Channel) ClientIsAtLeast(client *Client, permission modes.Mode) bool {
channel.stateMutex.RLock()
memberData := channel.members[client]
memberData, present := channel.members[client]
founder := channel.registeredFounder
channel.stateMutex.RUnlock()
@ -522,6 +509,10 @@ func (channel *Channel) ClientIsAtLeast(client *Client, permission modes.Mode) b
return true
}
if !present {
return false
}
for _, mode := range modes.ChannelUserModes {
if memberData.modes.HasMode(mode) {
return true
@ -571,20 +562,20 @@ func (channel *Channel) setMemberStatus(client *Client, status alwaysOnChannelSt
}
channel.stateMutex.Lock()
defer channel.stateMutex.Unlock()
if _, ok := channel.members[client]; !ok {
return
if mData, ok := channel.members[client]; ok {
mData.modes.Clear()
for _, mode := range status.Modes {
mData.modes.SetMode(modes.Mode(mode), true)
}
mData.joinTime = status.JoinTime
}
memberData := channel.members[client]
memberData.modes = newModes
memberData.joinTime = status.JoinTime
channel.members[client] = memberData
}
func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool {
channel.stateMutex.RLock()
founder := channel.registeredFounder
clientModes := channel.members[client].modes
targetModes := channel.members[target].modes
clientData, clientOK := channel.members[client]
targetData, targetOK := channel.members[target]
channel.stateMutex.RUnlock()
if founder != "" {
@ -595,7 +586,11 @@ func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool
}
}
return channelUserModeHasPrivsOver(clientModes.HighestChannelUserMode(), targetModes.HighestChannelUserMode())
return clientOK && targetOK &&
channelUserModeHasPrivsOver(
clientData.modes.HighestChannelUserMode(),
targetData.modes.HighestChannelUserMode(),
)
}
func (channel *Channel) hasClient(client *Client) bool {
@ -1319,9 +1314,9 @@ func (channel *Channel) SendSplitMessage(command string, minPrefixMode modes.Mod
if channel.flags.HasMode(modes.OpModerated) {
channel.stateMutex.RLock()
cuData := channel.members[client]
cuData, ok := channel.members[client]
channel.stateMutex.RUnlock()
if cuData.modes.HighestChannelUserMode() == modes.Mode(0) {
if !ok || cuData.modes.HighestChannelUserMode() == modes.Mode(0) {
// max(statusmsg_minmode, halfop)
if minPrefixMode == modes.Mode(0) || minPrefixMode == modes.Voice {
minPrefixMode = modes.Halfop
@ -1490,6 +1485,7 @@ func (channel *Channel) Purge(source string) {
chname := channel.name
members := channel.membersCache
channel.membersCache = nil
channel.memberDataCache = nil
channel.members = make(MemberSet)
// TODO try to prevent Purge racing against (pending) Join?
channel.stateMutex.Unlock()