carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package cgroup
2
3import (
4 "context"
5 "path/filepath"
6 "strings"
7 "sync"
8)
9
10// The in-flight registry: target cgroup directories that a shim is being
11// placed into, from just before its fork until it answers placed or the
12// start fails. With clone3 a child appeared in its cgroup atomically; a
13// shim spends a few milliseconds in the agent's cgroup first, so a proof
14// that a cgroup is empty must count these too (Ruling 6, Task 2). The flat
15// PID-move fallback (startAndMoveIn) registers its target as well, from
16// its start until its PID write has returned. Readers: Cleanup's
17// population and members (cleanup_linux.go) and ensureParent (gen.go).
18//
19// A registration ends only once its process is in the kernel's view of its
20// cgroup (or never will be), so a placement is always visible to one of the
21// two: the registry, or the kernel. But a reader that asks them one after
22// the other can miss a placement that moves from one to the other between
23// the two reads (whole-branch review, finding 1). Two tools close that:
24//
25// - readQuiet brackets one kernel read: it answers quiet only when no
26// placement into the directory or below it was in flight at any moment
27// of that read (none when it began, none registered or ended during it,
28// none at its end). A reader that is not quiet treats the directory as
29// populated, or its member list as unproven. Cleanup's reads use it.
30// - holdPlacements stops any placement into the directory or below it
31// from registering until it is released, so a decision taken under it
32// (ensureParent's cold parent) holds until its writes are done. It is
33// held across cgroupfs reads and writes only, never across a before
34// callback, a shim wait or a start.
35//
36// No lock of the registry is ever held across a before callback, a shim
37// wait or a start: placeMu guards only the maps, and a registration blocked
38// by a hold waits on a channel with placeMu released.
39
40// placementReadHook, when set, runs right after each kernel read that
41// readQuiet brackets, and after ensureParent's population read. Only test
42// files set it, to register or end a placement at exactly that point.
43var placementReadHook func(dir string)
44
45// placementHeldHook, when set, runs when a registration finds a hold over
46// its target and is about to wait for it. Only test files set it.
47var placementHeldHook func(dir string)
48
49// placeWindow is one readQuiet in progress: busy means a placement under
50// dir was in flight when it began; changed, that one registered or ended
51// under dir since.
52type placeWindow struct {
53 dir string
54 busy bool
55 changed bool
56}
57
58// beginPlacement registers one placement into dir. done ends it, and is
59// idempotent: a second call does nothing. A nil manager registers nothing.
60// While a hold covers dir (holdPlacements), it waits for the hold to end.
61func (m *Manager) beginPlacement(dir string) (done func()) {
62 if m == nil {
63 return func() {}
64 }
65 dir = filepath.Clean(dir)
66 m.placeMu.Lock()
67 for m.heldLocked(dir) {
68 wake := m.wakeLocked()
69 m.placeMu.Unlock()
70 if placementHeldHook != nil {
71 placementHeldHook(dir)
72 }
73 <-wake
74 m.placeMu.Lock()
75 }
76 if m.placing == nil {
77 m.placing = make(map[string]int)
78 }
79 m.placing[dir]++
80 m.touchLocked(dir)
81 m.placeMu.Unlock()
82 var once sync.Once
83 return func() {
84 once.Do(func() {
85 m.placeMu.Lock()
86 defer m.placeMu.Unlock()
87 if m.placing[dir]--; m.placing[dir] <= 0 {
88 delete(m.placing, dir)
89 }
90 m.touchLocked(dir)
91 // Wake every waiter; each rechecks its own subtree.
92 m.broadcastLocked()
93 })
94 }
95}
96
97// readQuiet runs read, a read of the kernel's view of dir, and reports
98// whether no placement into dir or below it was in flight at any moment
99// while read ran. Only a quiet read covers every process bound for dir: a
100// placement that ended before read began is in the kernel's view, and one
101// that registers after read ended had not forked while it ran. A nil
102// manager has no placements: always quiet.
103func (m *Manager) readQuiet(dir string, read func()) (quiet bool) {
104 dir = filepath.Clean(dir)
105 if m == nil {
106 read()
107 return true
108 }
109 w := &placeWindow{dir: dir}
110 m.placeMu.Lock()
111 w.busy = m.countUnderLocked(dir) > 0
112 if m.windows == nil {
113 m.windows = make(map[*placeWindow]struct{})
114 }
115 m.windows[w] = struct{}{}
116 m.placeMu.Unlock()
117 read()
118 if placementReadHook != nil {
119 placementReadHook(dir)
120 }
121 m.placeMu.Lock()
122 defer m.placeMu.Unlock()
123 delete(m.windows, w)
124 return !w.busy && !w.changed && m.countUnderLocked(dir) == 0
125}
126
127// holdPlacements keeps every placement into dir or below it from
128// registering until release is called, and answers the placements already
129// in flight: at, into dir itself, and under, into dir or below it. With
130// the hold in place those counts can only fall, and a count of zero stays
131// zero, so a kernel read taken under the hold, with both zero, sees every
132// process bound for dir. release is idempotent. Hold it only across
133// cgroupfs reads and writes.
134func (m *Manager) holdPlacements(dir string) (at, under int, release func()) {
135 if m == nil {
136 return 0, 0, func() {}
137 }
138 dir = filepath.Clean(dir)
139 m.placeMu.Lock()
140 if m.holds == nil {
141 m.holds = make(map[string]int)
142 }
143 m.holds[dir]++
144 at, under = m.placing[dir], m.countUnderLocked(dir)
145 m.placeMu.Unlock()
146 var once sync.Once
147 return at, under, func() {
148 once.Do(func() {
149 m.placeMu.Lock()
150 defer m.placeMu.Unlock()
151 if m.holds[dir]--; m.holds[dir] <= 0 {
152 delete(m.holds, dir)
153 }
154 m.broadcastLocked()
155 })
156 }
157}
158
159// placementsUnder counts placements into dir or anywhere below it.
160func (m *Manager) placementsUnder(dir string) int {
161 if m == nil {
162 return 0
163 }
164 m.placeMu.Lock()
165 defer m.placeMu.Unlock()
166 return m.countUnderLocked(filepath.Clean(dir))
167}
168
169// placementsAt counts placements into dir itself.
170func (m *Manager) placementsAt(dir string) int {
171 if m == nil {
172 return 0
173 }
174 m.placeMu.Lock()
175 defer m.placeMu.Unlock()
176 return m.placing[filepath.Clean(dir)]
177}
178
179// waitPlacementsUnder returns nil once no placement is in flight in dir or
180// below it, or ctx's error if ctx ends first.
181func (m *Manager) waitPlacementsUnder(ctx context.Context, dir string) error {
182 if m == nil {
183 return nil
184 }
185 dir = filepath.Clean(dir)
186 for {
187 m.placeMu.Lock()
188 if m.countUnderLocked(dir) == 0 {
189 m.placeMu.Unlock()
190 return nil
191 }
192 wake := m.wakeLocked()
193 m.placeMu.Unlock()
194 select {
195 case <-ctx.Done():
196 return ctx.Err()
197 case <-wake:
198 }
199 }
200}
201
202// wakeLocked is the channel the next broadcastLocked closes.
203func (m *Manager) wakeLocked() chan struct{} {
204 if m.placeWake == nil {
205 m.placeWake = make(chan struct{})
206 }
207 return m.placeWake
208}
209
210// broadcastLocked wakes every waiter: a registration ended, or a hold did.
211func (m *Manager) broadcastLocked() {
212 if m.placeWake != nil {
213 close(m.placeWake)
214 m.placeWake = nil
215 }
216}
217
218// touchLocked marks every open readQuiet over target as changed.
219func (m *Manager) touchLocked(target string) {
220 for w := range m.windows {
221 if isUnder(target, w.dir) {
222 w.changed = true
223 }
224 }
225}
226
227// heldLocked reports whether a hold covers target.
228func (m *Manager) heldLocked(target string) bool {
229 for h := range m.holds {
230 if isUnder(target, h) {
231 return true
232 }
233 }
234 return false
235}
236
237func (m *Manager) countUnderLocked(dir string) int {
238 n := 0
239 for target, c := range m.placing {
240 if isUnder(target, dir) {
241 n += c
242 }
243 }
244 return n
245}
246
247// isUnder reports whether target is dir or below it. Both are clean.
248func isUnder(target, dir string) bool {
249 prefix := dir
250 if !strings.HasSuffix(prefix, "/") {
251 prefix += "/"
252 }
253 return target == dir || strings.HasPrefix(target, prefix)
254}
255