mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
fix(stage): fix race condition with stages.
This commit is contained in:
@@ -111,10 +111,20 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
|
|||||||
if !s.userEnteredStage {
|
if !s.userEnteredStage {
|
||||||
s.userEnteredStage = true
|
s.userEnteredStage = true
|
||||||
|
|
||||||
|
// Lock server to safely iterate over sessions map
|
||||||
|
// We need to copy the session list first to avoid holding the lock during packet building
|
||||||
|
s.server.Lock()
|
||||||
|
var sessionList []*Session
|
||||||
for _, session := range s.server.sessions {
|
for _, session := range s.server.sessions {
|
||||||
if s == session {
|
if s == session {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
sessionList = append(sessionList, session)
|
||||||
|
}
|
||||||
|
s.server.Unlock()
|
||||||
|
|
||||||
|
// Build packets for each session without holding the lock
|
||||||
|
for _, session := range sessionList {
|
||||||
temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID}
|
temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID}
|
||||||
newNotif.WriteUint16(uint16(temp.Opcode()))
|
newNotif.WriteUint16(uint16(temp.Opcode()))
|
||||||
temp.Build(newNotif, s.clientContext)
|
temp.Build(newNotif, s.clientContext)
|
||||||
@@ -132,12 +142,22 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
|
|||||||
if s.stage != nil { // avoids lock up when using bed for dream quests
|
if s.stage != nil { // avoids lock up when using bed for dream quests
|
||||||
// Notify the client to duplicate the existing objects.
|
// Notify the client to duplicate the existing objects.
|
||||||
s.logger.Info(fmt.Sprintf("Sending existing stage objects to %s", s.Name))
|
s.logger.Info(fmt.Sprintf("Sending existing stage objects to %s", s.Name))
|
||||||
|
|
||||||
|
// Lock stage to safely iterate over objects map
|
||||||
|
// We need to copy the objects list first to avoid holding the lock during packet building
|
||||||
s.stage.RLock()
|
s.stage.RLock()
|
||||||
var temp mhfpacket.MHFPacket
|
var objectList []*Object
|
||||||
for _, obj := range s.stage.objects {
|
for _, obj := range s.stage.objects {
|
||||||
if obj.ownerCharID == s.charID {
|
if obj.ownerCharID == s.charID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
objectList = append(objectList, obj)
|
||||||
|
}
|
||||||
|
s.stage.RUnlock()
|
||||||
|
|
||||||
|
// Build packets for each object without holding the lock
|
||||||
|
var temp mhfpacket.MHFPacket
|
||||||
|
for _, obj := range objectList {
|
||||||
temp = &mhfpacket.MsgSysDuplicateObject{
|
temp = &mhfpacket.MsgSysDuplicateObject{
|
||||||
ObjID: obj.id,
|
ObjID: obj.id,
|
||||||
X: obj.x,
|
X: obj.x,
|
||||||
@@ -149,7 +169,6 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
|
|||||||
newNotif.WriteUint16(uint16(temp.Opcode()))
|
newNotif.WriteUint16(uint16(temp.Opcode()))
|
||||||
temp.Build(newNotif, s.clientContext)
|
temp.Build(newNotif, s.clientContext)
|
||||||
}
|
}
|
||||||
s.stage.RUnlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIX: Always send stage transfer packet, even if empty.
|
// FIX: Always send stage transfer packet, even if empty.
|
||||||
@@ -166,7 +185,12 @@ func destructEmptyStages(s *Session) {
|
|||||||
for _, stage := range s.server.stages {
|
for _, stage := range s.server.stages {
|
||||||
// Destroy empty Quest/My series/Guild stages.
|
// Destroy empty Quest/My series/Guild stages.
|
||||||
if stage.id[3:5] == "Qs" || stage.id[3:5] == "Ms" || stage.id[3:5] == "Gs" || stage.id[3:5] == "Ls" {
|
if stage.id[3:5] == "Qs" || stage.id[3:5] == "Ms" || stage.id[3:5] == "Gs" || stage.id[3:5] == "Ls" {
|
||||||
if len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0 {
|
// Lock stage to safely check its client and reservation counts
|
||||||
|
stage.Lock()
|
||||||
|
isEmpty := len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0
|
||||||
|
stage.Unlock()
|
||||||
|
|
||||||
|
if isEmpty {
|
||||||
delete(s.server.stages, stage.id)
|
delete(s.server.stages, stage.id)
|
||||||
s.logger.Debug("Destructed stage", zap.String("stage.id", stage.id))
|
s.logger.Debug("Destructed stage", zap.String("stage.id", stage.id))
|
||||||
}
|
}
|
||||||
@@ -183,6 +207,7 @@ func removeSessionFromStage(s *Session) {
|
|||||||
delete(s.stage.clients, s)
|
delete(s.stage.clients, s)
|
||||||
|
|
||||||
// Collect objects to delete while holding lock
|
// Collect objects to delete while holding lock
|
||||||
|
// We must copy the objects to delete to avoid modifying the map while iterating
|
||||||
s.logger.Info("Sending notification to old stage clients")
|
s.logger.Info("Sending notification to old stage clients")
|
||||||
var objectsToDelete []*Object
|
var objectsToDelete []*Object
|
||||||
for _, object := range s.stage.objects {
|
for _, object := range s.stage.objects {
|
||||||
@@ -209,6 +234,30 @@ func removeSessionFromStage(s *Session) {
|
|||||||
destructEmptySemaphores(s)
|
destructEmptySemaphores(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isStageFull(s *Session, StageID string) bool {
|
||||||
|
s.server.Lock()
|
||||||
|
stage, exists := s.server.stages[StageID]
|
||||||
|
s.server.Unlock()
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
// Lock stage to safely check client counts
|
||||||
|
// Read the values we need while holding RLock, then release immediately
|
||||||
|
// to avoid deadlock with other functions that might hold server lock
|
||||||
|
stage.RLock()
|
||||||
|
reserved := len(stage.reservedClientSlots)
|
||||||
|
clients := len(stage.clients)
|
||||||
|
_, hasReservation := stage.reservedClientSlots[s.charID]
|
||||||
|
maxPlayers := stage.maxPlayers
|
||||||
|
stage.RUnlock()
|
||||||
|
|
||||||
|
if hasReservation {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return reserved+clients >= int(maxPlayers)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgSysEnterStage)
|
pkt := p.(*mhfpacket.MsgSysEnterStage)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user