more cart
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -201,6 +202,9 @@ func (s *DiskStorage[V]) SaveLoop(duration time.Duration) {
|
||||
}
|
||||
|
||||
func (s *DiskStorage[V]) save() {
|
||||
if s.queue == nil {
|
||||
return
|
||||
}
|
||||
carts := 0
|
||||
lines := 0
|
||||
s.queue.Range(func(key, value any) bool {
|
||||
@@ -295,3 +299,65 @@ func (s *DiskStorage[V]) openWriterCount() int {
|
||||
defer s.writersMu.Unlock()
|
||||
return len(s.writers)
|
||||
}
|
||||
|
||||
func (s *DiskStorage[V]) PurgeOlderThan(ctx context.Context, retention time.Duration, isGrainActive func(id uint64) bool) (int, error) {
|
||||
if err := s.ensureDir(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(s.path)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("read storage dir: %w", err)
|
||||
}
|
||||
|
||||
cutoff := time.Now().Add(-retention)
|
||||
purgedCount := 0
|
||||
|
||||
for _, entry := range files {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := entry.Name()
|
||||
if !strings.HasSuffix(name, ".events.log") {
|
||||
continue
|
||||
}
|
||||
|
||||
var id uint64
|
||||
_, err := fmt.Sscanf(name, "%d.events.log", &id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
log.Printf("failed to get file info for %s: %v", name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if info.ModTime().Before(cutoff) {
|
||||
// Check if grain is active in memory
|
||||
if isGrainActive != nil && isGrainActive(id) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if we have an active writer open
|
||||
s.writersMu.Lock()
|
||||
_, hasWriter := s.writers[id]
|
||||
s.writersMu.Unlock()
|
||||
if hasWriter {
|
||||
continue
|
||||
}
|
||||
|
||||
// Delete the file
|
||||
fullPath := filepath.Join(s.path, name)
|
||||
if err := os.Remove(fullPath); err != nil {
|
||||
log.Printf("failed to delete expired cart log %s: %v", fullPath, err)
|
||||
} else {
|
||||
purgedCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return purgedCount, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package actor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -317,3 +318,86 @@ func TestDiskStorageConcurrentWritesDifferentIDs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiskStoragePurge(t *testing.T) {
|
||||
reg := diskTestRegistry(t)
|
||||
dir := t.TempDir()
|
||||
storage := NewDiskStorage[testState](dir, reg)
|
||||
t.Cleanup(storage.Close)
|
||||
|
||||
ctx := context.Background()
|
||||
msg := &cart_messages.AddItem{ItemId: 1, Quantity: 1}
|
||||
|
||||
// Write mutations to create event logs for 1, 2, 3, 4
|
||||
ids := []uint64{1, 2, 3, 4}
|
||||
for _, id := range ids {
|
||||
if err := storage.AppendMutations(id, msg); err != nil {
|
||||
t.Fatalf("append %d: %v", id, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Force save to disk
|
||||
storage.save()
|
||||
|
||||
// 1. Grain 1: Old modification time, inactive, no writer -> should be purged
|
||||
path1 := storage.logPath(1)
|
||||
oldTime := time.Now().Add(-10 * 24 * time.Hour)
|
||||
if err := os.Chtimes(path1, oldTime, oldTime); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// We must close the writer for grain 1, 2, 4 to make them eligible for purging (no open writer)
|
||||
storage.writersMu.Lock()
|
||||
for _, id := range []uint64{1, 2, 4} {
|
||||
if w, ok := storage.writers[id]; ok {
|
||||
w.purged = true
|
||||
w.file.Close()
|
||||
delete(storage.writers, id)
|
||||
}
|
||||
}
|
||||
storage.writersMu.Unlock()
|
||||
|
||||
// 2. Grain 2: Old mod time, but marked active -> should NOT be purged
|
||||
path2 := storage.logPath(2)
|
||||
if err := os.Chtimes(path2, oldTime, oldTime); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// 3. Grain 3: Old mod time, but has open writer -> should NOT be purged
|
||||
path3 := storage.logPath(3)
|
||||
if err := os.Chtimes(path3, oldTime, oldTime); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// 4. Grain 4: Recent mod time -> should NOT be purged
|
||||
path4 := storage.logPath(4)
|
||||
|
||||
activeGrains := map[uint64]bool{2: true}
|
||||
isGrainActive := func(id uint64) bool {
|
||||
return activeGrains[id]
|
||||
}
|
||||
|
||||
purged, err := storage.PurgeOlderThan(ctx, 5*24*time.Hour, isGrainActive)
|
||||
if err != nil {
|
||||
t.Fatalf("PurgeOlderThan failed: %v", err)
|
||||
}
|
||||
|
||||
if purged != 1 {
|
||||
t.Errorf("Expected exactly 1 purged file, got %d", purged)
|
||||
}
|
||||
|
||||
// Verify file status
|
||||
if _, err := os.Stat(path1); !os.IsNotExist(err) {
|
||||
t.Error("Expected grain 1 log to be deleted")
|
||||
}
|
||||
if _, err := os.Stat(path2); err != nil {
|
||||
t.Error("Expected grain 2 log to exist (active)")
|
||||
}
|
||||
if _, err := os.Stat(path3); err != nil {
|
||||
t.Error("Expected grain 3 log to exist (active writer)")
|
||||
}
|
||||
if _, err := os.Stat(path4); err != nil {
|
||||
t.Error("Expected grain 4 log to exist (recent)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user