Fixed few memory leaks. Implement testing of the functionality.
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user