Files
Assistant/runtime/supervisor/durable_store.go
2026-08-29 11:47:05 +03:00

95 lines
2.6 KiB
Go

package supervisor
import (
"encoding/json"
"errors"
"os"
"path/filepath"
)
// DurableStore is supervisor-owned external control-plane state. It is not a
// candidate Assistant process-local recovery cache.
type DurableStore struct{ path string }
func NewDurableStore(path string) (*DurableStore, error) {
if path == "" || !filepath.IsAbs(path) {
return nil, errors.New("supervisor state path must be absolute")
}
return &DurableStore{path: path}, nil
}
func (s *DurableStore) Save(value Lifeboat) error {
if s == nil || s.path == "" {
return errors.New("supervisor state store is unavailable")
}
payload, err := json.Marshal(value)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(s.path), 0700); err != nil {
return err
}
temporary, err := os.CreateTemp(filepath.Dir(s.path), ".lifeboat-*.tmp")
if err != nil {
return err
}
temporaryName := temporary.Name()
defer os.Remove(temporaryName)
if err = temporary.Chmod(0600); err == nil {
_, err = temporary.Write(payload)
}
if closeErr := temporary.Close(); err == nil {
err = closeErr
}
if err != nil {
return err
}
return os.Rename(temporaryName, s.path)
}
func (s *DurableStore) Load() (Lifeboat, error) {
if s == nil || s.path == "" {
return Lifeboat{}, errors.New("supervisor state store is unavailable")
}
payload, err := os.ReadFile(s.path)
if err != nil {
return Lifeboat{}, err
}
var value Lifeboat
if err := json.Unmarshal(payload, &value); err != nil {
return Lifeboat{}, err
}
return value, nil
}
// PersistentLifeboat reloads before every transition and commits after it.
type PersistentLifeboat struct{ store *DurableStore }
func NewPersistentLifeboat(store *DurableStore) *PersistentLifeboat {
return &PersistentLifeboat{store: store}
}
func (p *PersistentLifeboat) transition(fn func(*Lifeboat) bool) (bool, error) {
value, err := p.store.Load()
if err != nil && !os.IsNotExist(err) {
return false, err
}
ok := fn(&value)
if !ok {
return false, nil
}
if err := p.store.Save(value); err != nil {
return false, err
}
return true, nil
}
func (p *PersistentLifeboat) Start(candidate Release) (bool, error) {
return p.transition(func(value *Lifeboat) bool { return value.Start(candidate) })
}
func (p *PersistentLifeboat) Record(observation Observation) (bool, error) {
return p.transition(func(value *Lifeboat) bool { return value.Record(observation) })
}
func (p *PersistentLifeboat) Rollback() (bool, error) {
return p.transition(func(value *Lifeboat) bool { return value.Rollback() })
}
func (p *PersistentLifeboat) Snapshot() (Lifeboat, error) { return p.store.Load() }