| 1 | package activator |
| 2 | |
| 3 | import "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. |
| 17 | type 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. |
| 26 | func (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. |
| 43 | func (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. |
| 66 | func (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). |
| 80 | func (f *schedFence) retire(gen uint64) { |
| 81 | f.mu.Lock() |
| 82 | f.retired[gen] = true |
| 83 | f.mu.Unlock() |
| 84 | } |
| 85 | |
| 86 | func (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. |
| 102 | func (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. |
| 113 | func (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 | |