carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package activator
2
3import "sync"
4
5// schedFence is one host's schedule write fence (zero-downtime spec §7.10,
6// final review F8). Every schedule write a control listener of the host
7// takes (a PUT's or DELETE's store checks and its mutation) runs inside it,
8// and an overlap's switch holds it exclusively: it waits for the writes in
9// flight, keeps new ones out, and latches the retiring generation in the
10// same step as the switch. A generation once retired stays refused, so a
11// handler that began before the switch, or that outlives the retiring
12// member's place in the operation set, can never write after it.
13//
14// Lock order: the switch takes the fence, then a.mu; a write holds no
15// lock while it runs, and never takes a.mu inside the fence, so the two
16// never invert. mu guards the fields; cond is on mu.
17type schedFence struct {
18 mu sync.Mutex
19 cond *sync.Cond
20 writes int // schedule writes in flight
21 switching bool // a switch holds the fence (or is waiting for its writes)
22 retired map[uint64]bool // generations switched away from: latched
23}
24
25// schedFenceFor is host's fence, made on first use.
26func (a *Service) schedFenceFor(host string) *schedFence {
27 a.schedMu.Lock()
28 defer a.schedMu.Unlock()
29 if a.schedFences == nil {
30 a.schedFences = map[string]*schedFence{}
31 }
32 f := a.schedFences[host]
33 if f == nil {
34 f = &schedFence{retired: map[uint64]bool{}}
35 f.cond = sync.NewCond(&f.mu)
36 a.schedFences[host] = f
37 }
38 return f
39}
40
41// write runs w for generation gen unless gen is retired, and reports
42// whether it ran. It waits while a switch holds the fence.
43func (f *schedFence) write(gen uint64, w func()) bool {
44 f.mu.Lock()
45 for f.switching {
46 f.cond.Wait()
47 }
48 if f.retired[gen] {
49 f.mu.Unlock()
50 return false
51 }
52 f.writes++
53 f.mu.Unlock()
54 defer func() {
55 f.mu.Lock()
56 f.writes--
57 f.cond.Broadcast()
58 f.mu.Unlock()
59 }()
60 w()
61 return true
62}
63
64// lock takes the fence for a switch: one switch at a time, and none while
65// a write runs. Never call it with a.mu held.
66func (f *schedFence) lock() {
67 f.mu.Lock()
68 for f.switching {
69 f.cond.Wait()
70 }
71 f.switching = true
72 for f.writes > 0 {
73 f.cond.Wait()
74 }
75 f.mu.Unlock()
76}
77
78// retire latches gen as switched away from. The caller holds the fence
79// (lock).
80func (f *schedFence) retire(gen uint64) {
81 f.mu.Lock()
82 f.retired[gen] = true
83 f.mu.Unlock()
84}
85
86func (f *schedFence) unlock() {
87 f.mu.Lock()
88 f.switching = false
89 f.cond.Broadcast()
90 f.mu.Unlock()
91}
92
93// ScheduleWrite is the control listeners' write fence (schedule.WriteFence,
94// bound per listener through ExecBackend.ScheduleFence): it runs write for
95// a schedule write made on the control socket of host's generation gen,
96// unless an overlap's switch has retired that generation, and reports
97// whether it ran. The switch waits for a write in flight, so the retirement
98// decision and the mutation are one step against it.
99//
100// With swaps off nothing is ever retired and nothing is serialized: write
101// runs at once, as before.
102func (a *Service) ScheduleWrite(host string, gen uint64, write func()) bool {
103 if !a.gens {
104 write()
105 return true
106 }
107 return a.schedFenceFor(host).write(gen, write)
108}
109
110// RetiredGen reports whether host's generation gen has been switched
111// away from (its schedule writes are refused for good). False with swaps
112// off.
113func (a *Service) RetiredGen(host string, gen uint64) bool {
114 if !a.gens {
115 return false
116 }
117 f := a.schedFenceFor(host)
118 f.mu.Lock()
119 defer f.mu.Unlock()
120 return f.retired[gen]
121}
122