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 (
4 "context"
5 "strings"
6 "sync"
7 "testing"
8 "time"
9)
10
11// restoreInFlightForTest reports whether host's entry has a parent restore
12// retry in flight.
13func (a *Service) restoreInFlightForTest(host string) bool {
14 a.mu.Lock()
15 defer a.mu.Unlock()
16 e := a.entries[host]
17 return e != nil && e.ops.restoring
18}
19
20// owedRestore leaves f's entry with a parent restore owed: an overlap whose
21// last member's restore failed (F11), so the parent stays marked raised.
22func owedRestore(t *testing.T, f *swapFixture) {
23 t.Helper()
24 f.b.gmu.Lock()
25 f.b.restoreFails = 1
26 f.b.gmu.Unlock()
27 if res := f.a.Swap(context.Background(), f.attempt(ModeOverlap), f.newest); res.Outcome != SwapDone {
28 t.Fatalf("Swap = %+v", res)
29 }
30 if !f.a.parentRaisedForTest(f.host) {
31 t.Fatal("fixture: the failed restore should leave the parent marked raised")
32 }
33}
34
35// gateRestores holds every later RestoreGenParent before its write and
36// returns the gate and the channel each held call signals on.
37func gateRestores(f *swapFixture) (gate, entered chan struct{}) {
38 gate, entered = make(chan struct{}), make(chan struct{}, 8)
39 f.b.gmu.Lock()
40 f.b.restoreGate, f.b.restoreEntered = gate, entered
41 f.b.gmu.Unlock()
42 return gate, entered
43}
44
45// joinSignal installs the barrier's join hook and returns the channel it
46// signals on when a stop waits on the operation set.
47func joinSignal(f *swapFixture) chan struct{} {
48 joining := make(chan struct{}, 1)
49 f.a.mu.Lock()
50 f.a.joinWaiting = func(_, what string) {
51 if what == "operation set" {
52 select {
53 case joining <- struct{}{}:
54 default:
55 }
56 }
57 }
58 f.a.mu.Unlock()
59 return joining
60}
61
62// Final review R2-2: a parent restore retry is a lifecycle operation of its
63// entry. A terminal stop joins one in flight before its lease release; a
64// stopping entry launches none; and a hold's stop joins it before its
65// pinned replacement is installed, so the old retry can never write the
66// old entry's single-generation tier after a later overlap raised the
67// host's parent for two children.
68func TestAParentRestoreRetryIsJoinedByTheEntrysStop(t *testing.T) {
69 t.Parallel()
70 t.Run("a terminal stop joins the retry in flight", func(t *testing.T) {
71 f := newSwapFixture(t)
72 owedRestore(t, f)
73 gate, entered := gateRestores(f)
74 f.a.sweepOnce()
75 <-entered // the retry is past its launch, before its write
76 joining := joinSignal(f)
77 rep := f.b.lastRep()
78 f.a.mu.Lock()
79 f.a.beginStopLocked(f.a.entries[f.host], StopIdle)
80 f.a.mu.Unlock()
81 waitFor(t, "the stop to join the retry or finish", func() bool {
82 select {
83 case <-joining:
84 return true
85 default:
86 }
87 return !f.a.entryPresent(f.host)
88 })
89 if !f.a.entryPresent(f.host) || rep.isClosed() {
90 t.Errorf("the stop finished (entry present %v, lease released %v) while the parent restore retry was in flight", f.a.entryPresent(f.host), rep.isClosed())
91 }
92 close(gate)
93 waitFor(t, "the stop to finish once the retry is done", func() bool { return !f.a.entryPresent(f.host) && rep.isClosed() })
94 })
95
96 t.Run("a stopping entry launches no retry", func(t *testing.T) {
97 f := newSwapFixture(t)
98 owedRestore(t, f)
99 gate, _ := gateRestores(f)
100 defer close(gate)
101 stopGate := make(chan struct{})
102 f.a.mu.Lock()
103 e := f.a.entries[f.host]
104 cur := e.proc.(*fakeProc)
105 f.a.mu.Unlock()
106 cur.setStopGate(stopGate)
107 f.a.mu.Lock()
108 f.a.beginStopLocked(e, StopIdle)
109 f.a.mu.Unlock()
110 // Past the barrier, inside the current generation's Stop.
111 waitFor(t, "the stop to reach the current generation", func() bool { return cur.stopEntered() > 0 })
112 f.a.sweepOnce()
113 if f.a.restoreInFlightForTest(f.host) {
114 t.Error("the sweep launched a parent restore retry for a stopping entry")
115 }
116 close(stopGate)
117 waitFor(t, "the stop to finish", func() bool { return !f.a.entryPresent(f.host) })
118 })
119
120 t.Run("a hold, its replacement and a later overlap", func(t *testing.T) {
121 f := newSwapFixture(t)
122 owedRestore(t, f)
123 gate, entered := gateRestores(f)
124 f.a.sweepOnce()
125 <-entered
126 joining := joinSignal(f)
127 f.a.mu.Lock()
128 oldE := f.a.entries[f.host]
129 f.a.mu.Unlock()
130 f.rev.Add(1)
131 results := make(chan SwapResult, 1)
132 go func() { results <- f.a.Swap(context.Background(), f.attempt(ModeHold), f.newest) }()
133 joined := false
134 waitFor(t, "the hold's stop to join the retry or install its replacement", func() bool {
135 select {
136 case <-joining:
137 joined = true
138 return true
139 default:
140 }
141 f.a.mu.Lock()
142 defer f.a.mu.Unlock()
143 return f.a.entries[f.host] != oldE
144 })
145 var once sync.Once
146 release := func() { once.Do(func() { close(gate) }) }
147 defer release()
148 if !joined {
149 t.Error("the hold's stop installed its pinned replacement while the old entry's parent restore retry was in flight")
150 } else {
151 release()
152 }
153 select {
154 case res := <-results:
155 if res.Outcome != SwapDone {
156 t.Fatalf("hold Swap = %+v", res)
157 }
158 case <-time.After(10 * time.Second):
159 t.Fatal("the hold never finished")
160 }
161 waitFor(t, "the replacement to be awake", func() bool { return f.a.entryAwake(f.host) })
162 // A later overlap on the replacement raises the host's parent for two
163 // children. An old retry still held lands now, if it can.
164 _, _, before := f.b.calls()
165 f.b.gmu.Lock()
166 f.b.onRaise = func() {
167 if joined {
168 return
169 }
170 release()
171 deadline := time.Now().Add(5 * time.Second)
172 for time.Now().Before(deadline) {
173 if _, _, n := f.b.calls(); n > before {
174 return
175 }
176 time.Sleep(time.Millisecond)
177 }
178 }
179 f.b.gmu.Unlock()
180 f.rev.Add(1)
181 if res := f.a.Swap(context.Background(), f.attempt(ModeOverlap), f.newest); res.Outcome != SwapDone {
182 t.Fatalf("later overlap Swap = %+v", res)
183 }
184 ev := f.b.events()
185 raise := -1
186 for i, x := range ev {
187 if strings.HasPrefix(x, "raise:") {
188 raise = i
189 }
190 }
191 for _, x := range ev[raise+1:] {
192 if strings.HasPrefix(x, "switch:") {
193 break
194 }
195 if strings.HasPrefix(x, "restore:") {
196 t.Fatalf("a parent restore (%s) was written after the later overlap raised the parent and before its switch, while two generations ran: %v", x, ev[raise:])
197 }
198 }
199 })
200}
201