summaryrefslogtreecommitdiffstats
path: root/back/qver/version.go
diff options
context:
space:
mode:
Diffstat (limited to 'back/qver/version.go')
-rw-r--r--back/qver/version.go178
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{}{}
+}