aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/app/state/multi.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-10-25 21:28:49 +0900
committerOphestra <cat@gensokyo.uk>2025-10-25 21:29:24 +0900
commit470e545d2761b77f95147f61f633f4d629750ee7 (patch)
tree5badf0a34ecaa1487e88ffe6aa0467f7d55cd4c4 /internal/app/state/multi.go
parent8d3381821f2a8d257a4c55c0dd311019bd0e78eb (diff)
internal/app/state: use internal/lockedfile
This is a pretty solid implementation backed by robust tests, with a much cleaner interface. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/app/state/multi.go')
-rw-r--r--internal/app/state/multi.go84
1 files changed, 17 insertions, 67 deletions
diff --git a/internal/app/state/multi.go b/internal/app/state/multi.go
index c9096ace..9824a8ee 100644
--- a/internal/app/state/multi.go
+++ b/internal/app/state/multi.go
@@ -9,12 +9,15 @@ import (
"path"
"strconv"
"sync"
- "syscall"
"hakurei.app/hst"
+ "hakurei.app/internal/lockedfile"
"hakurei.app/message"
)
+// multiLockFileName is the name of the file backing [lockedfile.Mutex] of a multiBackend.
+const multiLockFileName = "lock"
+
// fine-grained locking and access
type multiStore struct {
base string
@@ -44,19 +47,18 @@ func (s *multiStore) Do(identity int, f func(c Cursor)) (bool, error) {
return false, &hst.AppError{Step: "create store segment directory", Err: err}
}
- // open locker file
- if l, err := os.OpenFile(b.path+".lock", os.O_RDWR|os.O_CREATE, 0600); err != nil {
- s.backends.CompareAndDelete(identity, b)
- return false, &hst.AppError{Step: "open store segment lock file", Err: err}
- } else {
- b.lockfile = l
- }
+ // set up file-based mutex
+ b.lockfile = lockedfile.MutexAt(path.Join(b.path, multiLockFileName))
+
b.mu.Unlock()
}
// lock backend
- if err := b.lockFile(); err != nil {
+ if unlock, err := b.lockfile.Lock(); err != nil {
return false, &hst.AppError{Step: "lock store segment", Err: err}
+ } else {
+ // unlock backend after Do is complete
+ defer unlock()
}
// expose backend methods without exporting the pointer
@@ -66,10 +68,6 @@ func (s *multiStore) Do(identity int, f func(c Cursor)) (bool, error) {
// disable access to the backend on a best-effort basis
c.multiBackend = nil
- // unlock backend
- if err := b.unlockFile(); err != nil {
- return true, &hst.AppError{Step: "unlock store segment", Err: err}
- }
return true, nil
}
@@ -108,59 +106,17 @@ func (s *multiStore) List() ([]int, error) {
return append([]int(nil), aidsBuf...), nil
}
-func (s *multiStore) Close() error {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- var errs []error
- s.backends.Range(func(_, value any) bool {
- b := value.(*multiBackend)
- errs = append(errs, b.close())
- return true
- })
-
- return errors.Join(errs...)
-}
-
type multiBackend struct {
path string
// created/opened by prepare
- lockfile *os.File
+ lockfile *lockedfile.Mutex
mu sync.RWMutex
}
func (b *multiBackend) filename(id *hst.ID) string { return path.Join(b.path, id.String()) }
-func (b *multiBackend) lockFileAct(lt int) (err error) {
- op := "LockAct"
- switch lt {
- case syscall.LOCK_EX:
- op = "Lock"
- case syscall.LOCK_UN:
- op = "Unlock"
- }
-
- for {
- err = syscall.Flock(int(b.lockfile.Fd()), lt)
- if !errors.Is(err, syscall.EINTR) {
- break
- }
- }
- if err != nil {
- return &fs.PathError{
- Op: op,
- Path: b.lockfile.Name(),
- Err: err,
- }
- }
- return nil
-}
-
-func (b *multiBackend) lockFile() error { return b.lockFileAct(syscall.LOCK_EX) }
-func (b *multiBackend) unlockFile() error { return b.lockFileAct(syscall.LOCK_UN) }
-
// reads all launchers in simpleBackend
// file contents are ignored if decode is false
func (b *multiBackend) load(decode bool) (map[hst.ID]*hst.State, error) {
@@ -184,6 +140,11 @@ func (b *multiBackend) load(decode bool) (map[hst.ID]*hst.State, error) {
return nil, fmt.Errorf("unexpected directory %q in store", e.Name())
}
+ // skip lock file
+ if e.Name() == multiLockFileName {
+ continue
+ }
+
var id hst.ID
if err := id.UnmarshalText([]byte(e.Name())); err != nil {
return nil, &hst.AppError{Step: "parse state key", Err: err}
@@ -268,17 +229,6 @@ func (b *multiBackend) Len() (int, error) {
return len(rn), nil
}
-func (b *multiBackend) close() error {
- b.mu.Lock()
- defer b.mu.Unlock()
-
- err := b.lockfile.Close()
- if err == nil || errors.Is(err, os.ErrInvalid) || errors.Is(err, os.ErrClosed) {
- return nil
- }
- return &hst.AppError{Step: "close lock file", Err: err}
-}
-
// NewMulti returns an instance of the multi-file store.
func NewMulti(msg message.Msg, runDir string) Store {
return &multiStore{