feat(channelserver): decouple channel servers for independent operation (#33)

Enable multiple Erupe instances to share a single PostgreSQL database
without destroying each other's state, fix existing data races in
cross-channel access, and lay groundwork for future distributed
channel server deployments.

Phase 1 — DB safety:
- Scope DELETE FROM servers/sign_sessions to this instance's server IDs
- Fix ci++ bug where failed channel start shifted subsequent IDs

Phase 2 — Fix data races in cross-channel access:
- Lock sessions map in FindSessionByCharID and DisconnectUser
- Lock stagesLock in handleMsgSysLockGlobalSema
- Snapshot sessions/stages under lock in TransitMessage types 1-4
- Lock channel when finding mail notification targets

Phase 3 — ChannelRegistry interface:
- Define ChannelRegistry interface with 7 cross-channel operations
- Implement LocalChannelRegistry with proper locking
- Add SessionSnapshot/StageSnapshot immutable copy types
- Delegate WorldcastMHF, FindSessionByCharID, DisconnectUser to Registry
- Migrate LockGlobalSema and guild mail handlers to use Registry
- Add comprehensive tests including concurrent access

Phase 4 — Per-channel enable/disable:
- Add Enabled *bool to EntranceChannelInfo (nil defaults to true)
- Skip disabled channels in startup loop, preserving ID stability
- Add IsEnabled() helper with backward-compatible default
- Update config.example.json with Enabled field
This commit is contained in:
Houmgaor
2026-02-19 18:13:34 +01:00
parent ba9fce153d
commit 754b5a3bff
10 changed files with 661 additions and 79 deletions

View File

@@ -304,11 +304,26 @@ func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
} else {
_ = mail.Send(s, nil)
for _, channel := range s.server.Channels {
for _, session := range channel.sessions {
if session.charID == pkt.CharID {
SendMailNotification(s, &mail, session)
if s.server.Registry != nil {
s.server.Registry.NotifyMailToCharID(pkt.CharID, s, &mail)
} else {
// Fallback: find the target session under lock, then notify outside the lock.
var targetSession *Session
for _, channel := range s.server.Channels {
channel.Lock()
for _, session := range channel.sessions {
if session.charID == pkt.CharID {
targetSession = session
break
}
}
channel.Unlock()
if targetSession != nil {
break
}
}
if targetSession != nil {
SendMailNotification(s, &mail, targetSession)
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))