Fixed few memory leaks. Implement testing of the functionality.

This commit is contained in:
Kalzu Rekku
2026-01-08 18:55:32 +02:00
parent c663ec0431
commit 1130b7fb8c
10 changed files with 1334 additions and 13 deletions

View File

@@ -9,6 +9,8 @@ import (
"os"
"path/filepath"
"sync"
"syscall"
"time"
)
type User struct {
@@ -56,6 +58,50 @@ func (s *UserStore) hashUserID(userID string) string {
return hex.EncodeToString(hash[:])
}
// acquireFileLock attempts to acquire an exclusive lock on the store file
// Returns the file descriptor and an error if locking fails
func (s *UserStore) acquireFileLock(forWrite bool) (*os.File, error) {
lockPath := s.filePath + ".lock"
// Create lock file if it doesn't exist
lockFile, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600)
if err != nil {
return nil, err
}
// Try to acquire lock with timeout
lockType := syscall.LOCK_SH // Shared lock for reads
if forWrite {
lockType = syscall.LOCK_EX // Exclusive lock for writes
}
// Use non-blocking lock with retry
maxRetries := 10
for i := 0; i < maxRetries; i++ {
err = syscall.Flock(int(lockFile.Fd()), lockType|syscall.LOCK_NB)
if err == nil {
return lockFile, nil
}
if err != syscall.EWOULDBLOCK {
lockFile.Close()
return nil, err
}
// Wait and retry
time.Sleep(100 * time.Millisecond)
}
lockFile.Close()
return nil, syscall.EWOULDBLOCK
}
// releaseFileLock releases the file lock
func (s *UserStore) releaseFileLock(lockFile *os.File) {
if lockFile != nil {
syscall.Flock(int(lockFile.Fd()), syscall.LOCK_UN)
lockFile.Close()
}
}
func (s *UserStore) Reload() error {
s.mu.Lock()
defer s.mu.Unlock()
@@ -74,6 +120,15 @@ func (s *UserStore) loadCache() error {
}
func (s *UserStore) loadCacheInternal() error {
// Acquire shared lock for reading (allows multiple readers, blocks writers)
lockFile, err := s.acquireFileLock(false)
if err != nil {
logger.Warn("Failed to acquire read lock on user store: %v", err)
// Continue without lock - degraded mode
} else {
defer s.releaseFileLock(lockFile)
}
// Read encrypted store file
encryptedData, err := os.ReadFile(s.filePath)
if err != nil {
@@ -108,6 +163,14 @@ func (s *UserStore) loadCacheInternal() error {
}
func (s *UserStore) save() error {
// Acquire exclusive lock for writing (blocks all readers and writers)
lockFile, err := s.acquireFileLock(true)
if err != nil {
logger.Error("Failed to acquire write lock on user store: %v", err)
return err
}
defer s.releaseFileLock(lockFile)
// Build store structure from cache
store := encryptedStore{
Users: make([]encryptedUserEntry, 0, len(s.cache)),
@@ -130,10 +193,18 @@ func (s *UserStore) save() error {
return err
}
// Write to file
// Write to temp file first for atomic operation
tempPath := s.filePath + ".tmp"
logger.Info("Saving user store with %d entries", len(s.cache))
if err := os.WriteFile(s.filePath, encryptedData, 0600); err != nil {
logger.Error("Failed to write store file: %v", err)
if err := os.WriteFile(tempPath, encryptedData, 0600); err != nil {
logger.Error("Failed to write temp store file: %v", err)
return err
}
// Atomic rename
if err := os.Rename(tempPath, s.filePath); err != nil {
logger.Error("Failed to rename store file: %v", err)
os.Remove(tempPath)
return err
}