diff --git a/schemas/patch-schema/28-drop-transient-binary-columns.sql b/schemas/patch-schema/28-drop-transient-binary-columns.sql new file mode 100644 index 000000000..738ce5555 --- /dev/null +++ b/schemas/patch-schema/28-drop-transient-binary-columns.sql @@ -0,0 +1,7 @@ +-- Drop transient binary columns that are now memory-only. +-- UserBinary type2/type3 and characters.minidata are session state +-- resent by the client on every login; they do not need persistence. + +ALTER TABLE user_binary DROP COLUMN IF EXISTS type2; +ALTER TABLE user_binary DROP COLUMN IF EXISTS type3; +ALTER TABLE characters DROP COLUMN IF EXISTS minidata; diff --git a/server/channelserver/handlers_misc.go b/server/channelserver/handlers_misc.go index c8160dc64..a74d1878a 100644 --- a/server/channelserver/handlers_misc.go +++ b/server/channelserver/handlers_misc.go @@ -211,11 +211,12 @@ func handleMsgMhfUseUdShopCoin(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfGetEnhancedMinidata(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetEnhancedMinidata) - // this looks to be the detailed chunk of information you can pull up on players in town - var data []byte - err := s.server.db.QueryRow("SELECT minidata FROM characters WHERE id = $1", pkt.CharID).Scan(&data) - if err != nil { - s.logger.Error("Failed to load minidata") + + s.server.minidataLock.RLock() + data, ok := s.server.minidataParts[pkt.CharID] + s.server.minidataLock.RUnlock() + + if !ok { data = make([]byte, 1) } doAckBufSucceed(s, pkt.AckHandle, data) @@ -224,10 +225,11 @@ func handleMsgMhfGetEnhancedMinidata(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSetEnhancedMinidata(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSetEnhancedMinidata) dumpSaveData(s, pkt.RawDataPayload, "minidata") - _, err := s.server.db.Exec("UPDATE characters SET minidata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID) - if err != nil { - s.logger.Error("Failed to save minidata", zap.Error(err)) - } + + s.server.minidataLock.Lock() + s.server.minidataParts[s.charID] = pkt.RawDataPayload + s.server.minidataLock.Unlock() + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } diff --git a/server/channelserver/handlers_users.go b/server/channelserver/handlers_users.go index f84399a57..dff556472 100644 --- a/server/channelserver/handlers_users.go +++ b/server/channelserver/handlers_users.go @@ -1,8 +1,6 @@ package channelserver import ( - "fmt" - "erupe-ce/network/mhfpacket" "go.uber.org/zap" ) @@ -21,42 +19,21 @@ func handleMsgSysSetUserBinary(s *Session, p mhfpacket.MHFPacket) { s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: pkt.BinaryType}] = pkt.RawDataPayload s.server.userBinaryPartsLock.Unlock() - var exists []byte - err := s.server.db.QueryRow("SELECT type2 FROM user_binary WHERE id=$1", s.charID).Scan(&exists) - if err != nil { - if _, err := s.server.db.Exec("INSERT INTO user_binary (id) VALUES ($1)", s.charID); err != nil { - s.logger.Error("Failed to insert user binary", zap.Error(err)) - } - } - - if _, err := s.server.db.Exec(fmt.Sprintf("UPDATE user_binary SET type%d=$1 WHERE id=$2", pkt.BinaryType), pkt.RawDataPayload, s.charID); err != nil { - s.logger.Error("Failed to update user binary", zap.Error(err)) - } - - msg := &mhfpacket.MsgSysNotifyUserBinary{ + s.server.BroadcastMHF(&mhfpacket.MsgSysNotifyUserBinary{ CharID: s.charID, BinaryType: pkt.BinaryType, - } - - s.server.BroadcastMHF(msg, s) + }, s) } func handleMsgSysGetUserBinary(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysGetUserBinary) - // Try to get the data. s.server.userBinaryPartsLock.RLock() - defer s.server.userBinaryPartsLock.RUnlock() data, ok := s.server.userBinaryParts[userBinaryPartID{charID: pkt.CharID, index: pkt.BinaryType}] + s.server.userBinaryPartsLock.RUnlock() - // If we can't get the real data, try to get it from the database. if !ok { - err := s.server.db.QueryRow(fmt.Sprintf("SELECT type%d FROM user_binary WHERE id=$1", pkt.BinaryType), pkt.CharID).Scan(&data) - if err != nil { - doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) - } else { - doAckBufSucceed(s, pkt.AckHandle, data) - } + doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) } else { doAckBufSucceed(s, pkt.AckHandle, data) } diff --git a/server/channelserver/handlers_users_test.go b/server/channelserver/handlers_users_test.go index 5885781cc..fffe786d9 100644 --- a/server/channelserver/handlers_users_test.go +++ b/server/channelserver/handlers_users_test.go @@ -81,23 +81,23 @@ func TestHandleMsgSysGetUserBinary_NotInCache(t *testing.T) { server.userBinaryParts = make(map[userBinaryPartID][]byte) session := createMockSession(1, server) - // Don't populate cache - will fall back to DB (which is nil in test) pkt := &mhfpacket.MsgSysGetUserBinary{ AckHandle: 12345, CharID: 100, BinaryType: 1, } - // This will panic when trying to access nil db, which is expected - // in the test environment without database setup - defer func() { - if r := recover(); r != nil { - // Expected - no database in test - t.Log("Expected panic due to nil database in test") - } - }() - handleMsgSysGetUserBinary(session, pkt) + + // Should return a fail ACK (no DB fallback, just cache miss) + select { + case p := <-session.sendPackets: + if len(p.data) == 0 { + t.Error("Response packet should have data") + } + default: + t.Error("No response packet queued") + } } func TestUserBinaryPartID_AsMapKey(t *testing.T) { diff --git a/server/channelserver/session_lifecycle_integration_test.go b/server/channelserver/session_lifecycle_integration_test.go index 7cba56146..d433e5307 100644 --- a/server/channelserver/session_lifecycle_integration_test.go +++ b/server/channelserver/session_lifecycle_integration_test.go @@ -584,6 +584,7 @@ func createTestServerWithDB(t *testing.T, db *sqlx.DB) *Server { sessions: make(map[net.Conn]*Session), stages: make(map[string]*Stage), userBinaryParts: make(map[userBinaryPartID][]byte), + minidataParts: make(map[uint32][]byte), semaphore: make(map[string]*Semaphore), erupeConfig: _config.ErupeConfig, isShuttingDown: false, diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index c4349d2e8..d951f93d0 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -60,6 +60,10 @@ type Server struct { userBinaryPartsLock sync.RWMutex userBinaryParts map[userBinaryPartID][]byte + // EnhancedMinidata + minidataLock sync.RWMutex + minidataParts map[uint32][]byte + // Semaphore semaphoreLock sync.RWMutex semaphore map[string]*Semaphore @@ -89,6 +93,7 @@ func NewServer(config *Config) *Server { sessions: make(map[net.Conn]*Session), stages: make(map[string]*Stage), userBinaryParts: make(map[userBinaryPartID][]byte), + minidataParts: make(map[uint32][]byte), semaphore: make(map[string]*Semaphore), semaphoreIndex: 7, discordBot: config.DiscordBot,