summaryrefslogtreecommitdiffstats
path: root/back/qver/version.go
blob: 8c815cf16c5bfbb27b0f1af980e49012fd14a3fa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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{}{}
}