aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/store/segment.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/store/segment.go')
-rw-r--r--internal/store/segment.go104
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
+}