forked from security/hakurei
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>
This commit is contained in:
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user