internal/store: rename from state
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m9s
Test / Hakurei (push) Successful in 3m8s
Test / Hpkg (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 4m7s
Test / Hakurei (race detector) (push) Successful in 4m55s
Test / Flake checks (push) Successful in 1m25s

This reduces collision with local variable names, and generally makes sense for the new store package, since it no longer specifies the state struct.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2025-10-30 18:43:55 +09:00
parent 46c5ce4936
commit ebdcff1049
16 changed files with 34 additions and 35 deletions

161
internal/store/segment.go Normal file
View File

@@ -0,0 +1,161 @@
package store
import (
"errors"
"fmt"
"iter"
"os"
"strconv"
"sync"
"hakurei.app/container/check"
"hakurei.app/hst"
"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 {
// Error returned while decoding pathname.
// A non-nil value disables stateEntryHandle.
decodeErr error
// Checked path 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
}
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 does not go through open
if eh.decodeErr != nil {
return eh.decodeErr
}
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.
// 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 {
f, err := eh.open(os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if err != nil {
return err
}
err = entryEncode(f, state)
if closeErr := f.Close(); closeErr != nil && err == nil {
err = &hst.AppError{Step: "close state file", Err: closeErr}
}
return err
}
// 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) {
f, err := eh.open(os.O_RDONLY, 0)
if err != nil {
return 0, err
}
var et hst.Enablement
if v != nil {
et, err = entryDecode(f, v)
if err == nil && v.ID != eh.ID {
err = &hst.AppError{Step: "validate state identifier", Err: os.ErrInvalid,
Msg: fmt.Sprintf("state entry %s has unexpected id %s", eh.ID.String(), v.ID.String())}
}
} else {
et, err = entryDecodeHeader(f)
}
if closeErr := f.Close(); closeErr != nil && err == nil {
err = &hst.AppError{Step: "close state file", Err: closeErr}
}
return et, err
}
// storeHandle is a handle on a stateStore segment.
// Initialised by stateStore.identityHandle.
type storeHandle 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
// Inter-process mutex to synchronise operations against resources in this segment.
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) {
// 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 {
return nil, -1, &hst.AppError{Step: step, Err: err}
} else {
entries = pl
}
// expects lock file
l := len(entries)
if l > 0 {
l--
}
return func(yield func(*stateEntryHandle) bool) {
for _, ent := range entries {
var eh = stateEntryHandle{pathname: h.path.Append(ent.Name())}
// this should never happen
if ent.IsDir() {
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 {
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}
goto out
}
out:
if !yield(&eh) {
break
}
}
}, l, nil
}