diff options
Diffstat (limited to 'internal/store/segment.go')
| -rw-r--r-- | internal/store/segment.go | 104 |
1 files changed, 63 insertions, 41 deletions
diff --git a/internal/store/segment.go b/internal/store/segment.go index ef3e20c7..38859590 100644 --- a/internal/store/segment.go +++ b/internal/store/segment.go @@ -13,49 +13,52 @@ import ( "hakurei.app/internal/lockedfile" ) -// stateEntryHandle is a handle on a state entry retrieved from a storeHandle. -// Must only be used while its parent storeHandle.fileMu is held. -type stateEntryHandle struct { +// EntryHandle is a handle on a state entry retrieved from a [Handle]. +// Must only be used while its parent [Handle.Lock] is held. +type EntryHandle struct { // Error returned while decoding pathname. - // A non-nil value disables stateEntryHandle. - decodeErr error + // A non-nil value disables EntryHandle. + DecodeErr error - // Checked path to entry file. - pathname *check.Absolute + // Checked pathname to entry file. + Pathname *check.Absolute hst.ID } -// open opens the underlying state entry file, returning [hst.AppError] for a non-nil error. -func (eh *stateEntryHandle) open(flag int, perm os.FileMode) (*os.File, error) { - if eh.decodeErr != nil { - return nil, eh.decodeErr +// open opens the underlying state entry file. +// A non-nil error returned by open is of type [hst.AppError]. +func (eh *EntryHandle) open(flag int, perm os.FileMode) (*os.File, error) { + if eh.DecodeErr != nil { + return nil, eh.DecodeErr } - if f, err := os.OpenFile(eh.pathname.String(), flag, perm); err != nil { + if f, err := os.OpenFile(eh.Pathname.String(), flag, perm); err != nil { return nil, &hst.AppError{Step: "open state entry", Err: err} } else { return f, nil } } -// destroy removes the underlying state entry file, returning [hst.AppError] for a non-nil error. -func (eh *stateEntryHandle) destroy() error { +// Destroy removes the underlying state entry. +// A non-nil error returned by Destroy is of type [hst.AppError]. +func (eh *EntryHandle) Destroy() error { // destroy does not go through open - if eh.decodeErr != nil { - return eh.decodeErr + if eh.DecodeErr != nil { + return eh.DecodeErr } - if err := os.Remove(eh.pathname.String()); err != nil { + if err := os.Remove(eh.Pathname.String()); err != nil { return &hst.AppError{Step: "destroy state entry", Err: err} } return nil } -// save encodes [hst.State] and writes it to the underlying file. +// Save encodes [hst.State] and writes it to the underlying file. // An error is returned if a file already exists with the same identifier. -// save does not validate the embedded [hst.Config]. -func (eh *stateEntryHandle) save(state *hst.State) error { +// Save does not validate the embedded [hst.Config]. +// A non-nil error returned by Save is of type [hst.AppError]. +func (eh *EntryHandle) Save(state *hst.State) error { f, err := eh.open(os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) if err != nil { return err @@ -68,10 +71,11 @@ func (eh *stateEntryHandle) save(state *hst.State) error { return err } -// load loads and validates the state entry header, and returns the [hst.Enablement] byte. +// Load loads and validates the state entry header, and returns the [hst.Enablement] byte. // for a non-nil v, the full state payload is decoded and stored in the value pointed to by v. -// load validates the embedded hst.Config value. -func (eh *stateEntryHandle) load(v *hst.State) (hst.Enablement, error) { +// Load validates the embedded [hst.Config] value. +// A non-nil error returned by Load is of type [hst.AppError]. +func (eh *EntryHandle) Load(v *hst.State) (hst.Enablement, error) { f, err := eh.open(os.O_RDONLY, 0) if err != nil { return 0, err @@ -94,31 +98,42 @@ func (eh *stateEntryHandle) load(v *hst.State) (hst.Enablement, error) { return et, err } -// storeHandle is a handle on a stateStore segment. -// Initialised by stateStore.identityHandle. -type storeHandle struct { +// Handle is a handle on a [Store] segment. +// Initialised by [Store.Handle]. +type Handle struct { // Identity of instances tracked by this segment. - identity int - // Pathname of directory that the segment referred to by storeHandle is rooted in. - path *check.Absolute + Identity int + // Pathname of directory that the segment referred to by Handle is rooted in. + Path *check.Absolute + // Inter-process mutex to synchronise operations against resources in this segment. + // Must not be held directly, callers should use [Handle.Lock] instead. fileMu *lockedfile.Mutex - // Must be held alongside fileMu. mu sync.Mutex } -// entries returns an iterator over all stateEntryHandle held in this segment. -// Must be called while holding a lock on mu and fileMu. -// A non-nil error attached to a stateEntryHandle indicates a malformed identifier and is of type [hst.AppError]. -// A non-nil error returned by entries is of type [hst.AppError]. -func (h *storeHandle) entries() (iter.Seq[*stateEntryHandle], int, error) { +// Lock attempts to acquire a lock on [Handle]. +// If successful, Lock returns a non-nil unlock function. +// A non-nil error returned by Lock is of type [hst.AppError]. +func (h *Handle) Lock() (unlock func(), err error) { + if unlock, err = h.fileMu.Lock(); err != nil { + return nil, &hst.AppError{Step: "acquire lock on store segment " + strconv.Itoa(h.Identity), Err: err} + } + return +} + +// Entries returns an iterator over all [EntryHandle] held in this segment. +// Must be called while holding [Handle.Lock]. +// A non-nil error attached to a [EntryHandle] indicates a malformed identifier and is of type [hst.AppError]. +// A non-nil error returned by Entries is of type [hst.AppError]. +func (h *Handle) Entries() (iter.Seq[*EntryHandle], int, error) { // for error reporting const step = "read store segment entries" // read directory contents, should only contain storeMutexName and identifier var entries []os.DirEntry - if pl, err := os.ReadDir(h.path.String()); err != nil { + if pl, err := os.ReadDir(h.Path.String()); err != nil { return nil, -1, &hst.AppError{Step: step, Err: err} } else { entries = pl @@ -130,25 +145,25 @@ func (h *storeHandle) entries() (iter.Seq[*stateEntryHandle], int, error) { l-- } - return func(yield func(*stateEntryHandle) bool) { + return func(yield func(*EntryHandle) bool) { for _, ent := range entries { - var eh = stateEntryHandle{pathname: h.path.Append(ent.Name())} + var eh = EntryHandle{Pathname: h.Path.Append(ent.Name())} // this should never happen if ent.IsDir() { - eh.decodeErr = &hst.AppError{Step: step, + eh.DecodeErr = &hst.AppError{Step: step, Err: errors.New("unexpected directory " + strconv.Quote(ent.Name()) + " in store")} goto out } // silently skip lock file - if ent.Name() == storeMutexName { + if ent.Name() == MutexName { continue } // this either indicates a serious bug or external interference if err := eh.ID.UnmarshalText([]byte(ent.Name())); err != nil { - eh.decodeErr = &hst.AppError{Step: "decode store segment entry", Err: err} + eh.DecodeErr = &hst.AppError{Step: "decode store segment entry", Err: err} goto out } @@ -159,3 +174,10 @@ func (h *storeHandle) entries() (iter.Seq[*stateEntryHandle], int, error) { } }, l, nil } + +// newHandle returns the address of a new segment [Handle] rooted in base. +func newHandle(base *check.Absolute, identity int) *Handle { + h := Handle{Identity: identity, Path: base.Append(strconv.Itoa(identity))} + h.fileMu = lockedfile.MutexAt(h.Path.Append(MutexName).String()) + return &h +} |
