1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2025-01-07 12:38:05 +00:00

State: Fix data race between ready and guild create handler

This commit is contained in:
Maximilian von Lindern 2021-05-29 13:21:47 +02:00 committed by diamondburned
parent f8195f6e87
commit 56aaed3d60
3 changed files with 46 additions and 95 deletions

View file

@ -1,51 +0,0 @@
package moreatomic
import (
"sync"
"github.com/diamondburned/arikawa/v2/discord"
)
type GuildIDSet struct {
set map[discord.GuildID]struct{}
mut sync.Mutex
}
// NewGuildIDSet creates a new GuildIDSet.
func NewGuildIDSet() *GuildIDSet {
return &GuildIDSet{
set: make(map[discord.GuildID]struct{}),
}
}
// Add adds the passed discord.GuildID to the set.
func (s *GuildIDSet) Add(flake discord.GuildID) {
s.mut.Lock()
s.set[flake] = struct{}{}
s.mut.Unlock()
}
// Contains checks whether the passed discord.GuildID is present in the set.
func (s *GuildIDSet) Contains(flake discord.GuildID) (ok bool) {
s.mut.Lock()
defer s.mut.Unlock()
_, ok = s.set[flake]
return
}
// Delete deletes the passed discord.GuildID from the set and returns true if
// the element is present. If not, Delete is a no-op and returns false.
func (s *GuildIDSet) Delete(flake discord.GuildID) bool {
s.mut.Lock()
defer s.mut.Unlock()
if _, ok := s.set[flake]; ok {
delete(s.set, flake)
return true
}
return false
}

View file

@ -5,57 +5,58 @@ import (
) )
func (s *State) handleReady(ev *gateway.ReadyEvent) { func (s *State) handleReady(ev *gateway.ReadyEvent) {
s.guildMutex.Lock()
defer s.guildMutex.Unlock()
for _, g := range ev.Guilds { for _, g := range ev.Guilds {
// store this so we know when we need to dispatch a belated s.unreadyGuilds[g.ID] = struct{}{}
// GuildReadyEvent
if g.Unavailable {
s.unreadyGuilds.Add(g.ID)
} else {
s.Handler.Call(&GuildReadyEvent{
GuildCreateEvent: &g,
})
}
} }
} }
func (s *State) handleGuildCreate(ev *gateway.GuildCreateEvent) { func (s *State) handleGuildCreate(ev *gateway.GuildCreateEvent) {
switch { s.guildMutex.Lock()
// this guild was unavailable, but has come back online
case s.unavailableGuilds.Delete(ev.ID):
s.Handler.Call(&GuildAvailableEvent{
GuildCreateEvent: ev,
})
// the guild was already unavailable when connecting to the gateway var derivedEvent interface{}
// we can dispatch a belated GuildReadyEvent
case s.unreadyGuilds.Delete(ev.ID):
s.Handler.Call(&GuildReadyEvent{
GuildCreateEvent: ev,
})
// we don't know this guild, hence we just joined it // The guild was previously announced to us in the ready event, and has now
default: // become available.
s.Handler.Call(&GuildJoinEvent{ if _, ok := s.unreadyGuilds[ev.ID]; ok {
GuildCreateEvent: ev, delete(s.unreadyGuilds, ev.ID)
}) derivedEvent = &GuildReadyEvent{GuildCreateEvent: ev}
// The guild was previously announced as unavailable through a guild
// delete event, and has now become available again.
} else if _, ok = s.unavailableGuilds[ev.ID]; ok {
delete(s.unavailableGuilds, ev.ID)
derivedEvent = &GuildAvailableEvent{GuildCreateEvent: ev}
// We don't know this guild, hence it's new.
} else {
derivedEvent = &GuildJoinEvent{GuildCreateEvent: ev}
} }
// Unlock here already, so we don't block the mutex if there are
// long-blocking synchronous handlers.
s.guildMutex.Unlock()
s.Handler.Call(derivedEvent)
} }
func (s *State) handleGuildDelete(ev *gateway.GuildDeleteEvent) { func (s *State) handleGuildDelete(ev *gateway.GuildDeleteEvent) {
s.guildMutex.Lock()
// store this so we can later dispatch a GuildAvailableEvent, once the // store this so we can later dispatch a GuildAvailableEvent, once the
// guild becomes available again. // guild becomes available again.
if ev.Unavailable { if ev.Unavailable {
s.unavailableGuilds.Add(ev.ID) s.unavailableGuilds[ev.ID] = struct{}{}
s.guildMutex.Unlock()
s.Handler.Call(&GuildUnavailableEvent{ s.Handler.Call(&GuildUnavailableEvent{GuildDeleteEvent: ev})
GuildDeleteEvent: ev,
})
} else { } else {
// it might have been unavailable before we left // Possible scenario requiring this would be leaving the guild while
s.unavailableGuilds.Delete(ev.ID) // unavailable.
delete(s.unavailableGuilds, ev.ID)
s.guildMutex.Unlock()
s.Handler.Call(&GuildLeaveEvent{ s.Handler.Call(&GuildLeaveEvent{GuildDeleteEvent: ev})
GuildDeleteEvent: ev,
})
} }
} }

View file

@ -8,7 +8,6 @@ import (
"github.com/diamondburned/arikawa/v2/discord" "github.com/diamondburned/arikawa/v2/discord"
"github.com/diamondburned/arikawa/v2/gateway" "github.com/diamondburned/arikawa/v2/gateway"
"github.com/diamondburned/arikawa/v2/internal/moreatomic"
"github.com/diamondburned/arikawa/v2/session" "github.com/diamondburned/arikawa/v2/session"
"github.com/diamondburned/arikawa/v2/state/store" "github.com/diamondburned/arikawa/v2/state/store"
"github.com/diamondburned/arikawa/v2/state/store/defaultstore" "github.com/diamondburned/arikawa/v2/state/store/defaultstore"
@ -88,13 +87,14 @@ type State struct {
fewMutex *sync.Mutex fewMutex *sync.Mutex
// unavailableGuilds is a set of discord.GuildIDs of guilds that became // unavailableGuilds is a set of discord.GuildIDs of guilds that became
// unavailable when already connected to the gateway, i.e. sent in a // unavailable after connecting to the gateway, i.e. they were sent in a
// GuildUnavailableEvent. // GuildUnavailableEvent.
unavailableGuilds *moreatomic.GuildIDSet unavailableGuilds map[discord.GuildID]struct{}
// unreadyGuilds is a set of discord.GuildIDs of guilds that were // unreadyGuilds is a set of discord.GuildIDs of the guilds received during
// unavailable when connecting to the gateway, i.e. they had Unavailable // the Ready event. After receiving guild create events for those guilds,
// set to true during Ready. // they will be removed.
unreadyGuilds *moreatomic.GuildIDSet unreadyGuilds map[discord.GuildID]struct{}
guildMutex *sync.Mutex
} }
// New creates a new state. // New creates a new state.
@ -132,8 +132,9 @@ func NewFromSession(s *session.Session, cabinet store.Cabinet) *State {
readyMu: new(sync.Mutex), readyMu: new(sync.Mutex),
fewMessages: map[discord.ChannelID]struct{}{}, fewMessages: map[discord.ChannelID]struct{}{},
fewMutex: new(sync.Mutex), fewMutex: new(sync.Mutex),
unavailableGuilds: moreatomic.NewGuildIDSet(), unavailableGuilds: make(map[discord.GuildID]struct{}),
unreadyGuilds: moreatomic.NewGuildIDSet(), unreadyGuilds: make(map[discord.GuildID]struct{}),
guildMutex: new(sync.Mutex),
} }
state.hookSession() state.hookSession()
return state return state