diff options
| author | Sam Anthony <sam@samanthony.xyz> | 2026-03-14 10:13:55 -0400 |
|---|---|---|
| committer | Sam Anthony <sam@samanthony.xyz> | 2026-03-14 10:13:55 -0400 |
| commit | 7d622695ae19e518fded6aa5fbf001dae4652211 (patch) | |
| tree | cca62a7c6213c824adc0ad8447ff189cb106fcf0 /back/qver/version.go | |
| parent | 3b8368d665c8818b84557f54681c5ebab35ba22e (diff) | |
| download | buth-7d622695ae19e518fded6aa5fbf001dae4652211.zip | |
back: add qver package to track qid versions
Diffstat (limited to 'back/qver/version.go')
| -rw-r--r-- | back/qver/version.go | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/back/qver/version.go b/back/qver/version.go new file mode 100644 index 0000000..8c815cf --- /dev/null +++ b/back/qver/version.go @@ -0,0 +1,178 @@ +// Package qver keeps track of 9P qid versions. +package qver + +// Version is a 9P qid.version. +// +// A Version may have several child Versions, representing a directory +// hierarchy. The root version is incremented whenever any of the +// children's versions change. +// +// Version is safe to share between goroutines. +type Version struct { + req <-chan struct{} + res <-chan message + + bump chan<- struct{} + regChild chan<- childReg + kill chan<- struct{} +} + +type Bumper interface { + // Bump increments the version. + Bump() +} + +type version struct { + req chan<- struct{} + res chan<- message + + bump <-chan struct{} + regChild <-chan childReg + kill <-chan struct{} + + v uint32 + UpdateFunc + state interface{} + children []*child +} + +type childReg struct { + child *Version + done chan<- struct{} +} + +type child struct { + *Version + last uint32 +} + +type message struct { + ver uint32 + err error +} + +func New(opts ...Option) *Version { + o := parse(opts...) + + reqc := make(chan struct{}) + resc := make(chan message) + bumpc := make(chan struct{}) + childc := make(chan childReg) + killc := make(chan struct{}) + + go (&version{ + reqc, + resc, + bumpc, + childc, + killc, + 0, + o.UpdateFunc, + o.state, + nil, + }).run() + + v := &Version{reqc, resc, bumpc, childc, killc} + if o.parent != nil { + o.parent.register(v) + } + return v +} + +func (v *version) run() { + defer close(v.req) + defer close(v.res) + + for { + select { + case v.req <- struct{}{}: + err := v.update() + v.res <- message{v.v, err} + + case <-v.bump: + v.v++ + + case creg := <-v.regChild: + cver, _ := creg.child.Get() + v.children = append(v.children, &child{creg.child, cver}) + close(creg.done) + + case <-v.kill: + return + } + } +} + +func (v *version) update() error { + if v.UpdateFunc != nil { + var err error + v.v, v.state, err = v.UpdateFunc(v.v, v.state) + if err != nil { + return err + } + } + + var modified bool + for _, c := range v.children { + mod, err := c.update() + if err != nil { + return err + } + modified = modified || mod + } + if modified { + v.v++ + } + + return nil +} + +// Returns true if modified. +func (c *child) update() (bool, error) { + _, ok := <-c.req + if !ok { + // closed + return false, nil + } + + msg := <-c.res + if msg.err != nil { + return false, msg.err + } + if msg.ver != c.last { + // modified + c.last = msg.ver + return true, nil + } + + // not modified + return false, nil +} + +func (v *Version) register(child *Version) { + done := make(chan struct{}) + v.regChild <- childReg{child, done} + <-done +} + +func (v *Version) Close() { + v.kill <- struct{}{} + close(v.kill) + close(v.bump) + close(v.regChild) +} + +// Get returns the current version. +// +// If the Version was created with the Update option, Get calls the +// UpdateFunc and returns the new version. +func (v *Version) Get() (uint32, error) { + <-v.req + msg := <-v.res + return msg.ver, msg.err +} + +// Bump increments the version. +func (v *Version) Bump() { + v.bump <- struct{}{} +} |