Files
Erupe/server/channelserver/char_save_locks.go
Houmgaor 01b829d0e9 feat(savedata): add tier 2 integrity protections
Strengthen savedata persistence against corruption and race conditions:

- SHA-256 checksum: hash the decompressed blob on every save, store in
  new savedata_hash column, verify on load to detect silent corruption.
  Pre-existing characters with no hash are silently upgraded on next save.
- Atomic transactions: wrap character data + house data + hash + backup
  into a single DB transaction via SaveCharacterDataAtomic, so a crash
  mid-save never leaves partial state.
- Per-character save mutex: CharacterLocks (sync.Map of charID → Mutex)
  serializes concurrent saves for the same character, preventing races
  that could defeat corruption detection. Different characters remain
  fully independent.

Migration 0008 adds the savedata_hash column to the characters table.
2026-03-17 19:21:55 +01:00

26 lines
823 B
Go

package channelserver
import "sync"
// CharacterLocks provides per-character mutexes to serialize save operations.
// This prevents concurrent saves for the same character from racing, which
// could defeat corruption detection (e.g. house tier snapshot vs. write).
//
// The underlying sync.Map grows lazily — entries are created on first access
// and never removed (character IDs are bounded and reused across sessions).
type CharacterLocks struct {
m sync.Map // map[uint32]*sync.Mutex
}
// Lock acquires the mutex for the given character and returns an unlock function.
// Usage:
//
// unlock := s.server.charSaveLocks.Lock(charID)
// defer unlock()
func (cl *CharacterLocks) Lock(charID uint32) func() {
val, _ := cl.m.LoadOrStore(charID, &sync.Mutex{})
mu := val.(*sync.Mutex)
mu.Lock()
return mu.Unlock
}