repository.go RawHistoryFolder
1package snapshot
2
3import (
4 "log/slog"
5 "sync"
6
7 "github.com/bnema/vev/internal/ports"
8)
9
10const (
11 repositorySessionsDir = "sessions"
12 repositoryObjectsDir = "objects"
13 repositoryGenerations = "generations"
14 repositoryHead = "HEAD"
15 generationWidth = 20
16 maxRepositoryRead = maxSnapshotFileSize
17)
18
19// Repository is the crash-safe, content-addressed session snapshot store.
20type Repository struct {
21 dir string
22 log *slog.Logger
23
24 // sessionStateMu owns locks. A caller retains a reference before it waits
25 // on a session mutex, so an idle entry can never be removed while a waiter
26 // still holds its old mutex.
27 sessionStateMu sync.Mutex
28 locks map[string]*sessionMutex
29}
30
31var _ ports.SnapshotRepository = (*Repository)(nil)
32
33// NewRepository creates a repository rooted at dir with the default logger. It
34// does not create files until the first publication, so construction is side-effect free.
35func NewRepository(dir string) *Repository {
36 return NewRepositoryWithLogger(dir, slog.Default())
37}
38
39// NewRepositoryWithLogger creates a repository rooted at dir with log.
40func NewRepositoryWithLogger(dir string, log *slog.Logger) *Repository {
41 if log == nil {
42 log = slog.Default()
43 }
44 return &Repository{
45 dir: dir,
46 log: log,
47 locks: make(map[string]*sessionMutex),
48 }
49}
50
51// sessionMutex is retained by each waiter. That reference makes eviction
52// safe: a new caller cannot lock a replacement mutex until all users of the
53// prior mutex have left it.
54type sessionMutex struct {
55 key string
56 mu sync.Mutex
57 references int
58}
59
60func (r *Repository) lockSession(key string) *sessionMutex {
61 r.sessionStateMu.Lock()
62 lock := r.locks[key]
63 if lock == nil {
64 lock = &sessionMutex{key: key}
65 r.locks[key] = lock
66 }
67 lock.references++
68 r.sessionStateMu.Unlock()
69 lock.mu.Lock()
70 return lock
71}
72
73func (r *Repository) unlockSession(lock *sessionMutex) {
74 lock.mu.Unlock()
75 r.releaseSessionReference(lock)
76}
77
78func (r *Repository) releaseSessionReference(lock *sessionMutex) {
79 r.sessionStateMu.Lock()
80 lock.references--
81 if lock.references < 0 {
82 r.sessionStateMu.Unlock()
83 panic("snapshot: release of unretained session lock")
84 }
85 if lock.references == 0 {
86 if r.locks[lock.key] != lock {
87 r.sessionStateMu.Unlock()
88 panic("snapshot: session lock registry mismatch")
89 }
90 delete(r.locks, lock.key)
91 }
92 r.sessionStateMu.Unlock()
93}