go.uber.org/yarpc@v1.72.1/peer/pendingheap/heap_test.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package pendingheap 22 23 import ( 24 "context" 25 "fmt" 26 "testing" 27 28 "github.com/stretchr/testify/assert" 29 "github.com/stretchr/testify/require" 30 "go.uber.org/yarpc/api/peer" 31 "go.uber.org/yarpc/api/peer/peertest" 32 "go.uber.org/yarpc/api/transport" 33 "go.uber.org/yarpc/internal/testtime" 34 ) 35 36 func TestPeerHeapEmpty(t *testing.T) { 37 var ph pendingHeap 38 assert.Zero(t, ph.Len(), "New peer heap should be empty") 39 popAndVerifyHeap(t, &ph) 40 } 41 42 func TestRoundRobinHeapOrdering(t *testing.T) { 43 p1 := &peerScore{pending: 1} 44 p2 := &peerScore{pending: 2} 45 p3 := &peerScore{pending: 3} 46 47 // same pending as p3, but always pushed after p3, so it will be returned last. 48 p4 := &peerScore{pending: 3} 49 50 want := []*peerScore{p1, p2, p3, p4} 51 tests := [][]*peerScore{ 52 {p1, p2, p3, p4}, 53 {p3, p4, p2, p1}, 54 {p3, p1, p2, p4}, 55 } 56 57 for _, tt := range tests { 58 h := pendingHeap{ 59 nextRand: nextRand(0), /* irrelevant since we're not doing random insertions*/ 60 } 61 for _, ps := range tt { 62 h.pushPeer(ps) 63 } 64 65 popped := popAndVerifyHeap(t, &h) 66 assert.Equal(t, want, popped, "Unexpected ordering of peers") 67 } 68 } 69 70 func TestPeerHeapInsertionOrdering(t *testing.T) { 71 p1 := &peerScore{pending: 1} 72 p2 := &peerScore{pending: 2} 73 p3 := &peerScore{pending: 3} 74 p4 := &peerScore{pending: 3} // same pending as p3 75 76 tests := []struct { 77 name string 78 give []*peerScore 79 nextRandIndex int 80 insert *peerScore 81 want []*peerScore 82 }{ 83 { 84 name: "p3.last < p4.last", 85 give: []*peerScore{p1, p2, p3}, 86 insert: p4, 87 // no swap since nextRandIndex+1 == len(list) 88 nextRandIndex: 3, 89 want: []*peerScore{p1, p2, p3, p4}, 90 // p1.last = 1 91 // p2.last = 2 92 // p3.last = 3 93 // p4.last = 4 94 }, 95 { 96 name: "p4.last < p3.last", 97 give: []*peerScore{p1, p2, p3}, 98 insert: p4, 99 nextRandIndex: 0, // swap p1.last 100 want: []*peerScore{p1, p2, p4, p3}, 101 // p1.last = 4 102 // p2.last = 2 103 // p4.last = 1 104 // p3.last = 3 105 }, 106 } 107 108 for _, tt := range tests { 109 t.Run(tt.name, func(t *testing.T) { 110 h := pendingHeap{nextRand: nextRandFromSlice([]int{tt.nextRandIndex})} 111 // prepare list 112 for _, ps := range tt.give { 113 h.pushPeer(ps) 114 } 115 116 // new peer added 117 h.pushPeerRandom(tt.insert) 118 119 popped := popAndVerifyHeap(t, &h) 120 assert.Equal(t, tt.want, popped, "Unexpected ordering of peers") 121 }) 122 } 123 } 124 125 func TestPeerHeapUpdate(t *testing.T) { 126 h := pendingHeap{nextRand: nextRand(0)} 127 p1 := &peerScore{pending: 1} 128 p2 := &peerScore{pending: 2} 129 p3 := &peerScore{pending: 3} 130 131 h.pushPeer(p3) 132 h.pushPeer(p1) 133 h.pushPeer(p2) 134 135 ps, ok := h.popPeer() 136 require.True(t, ok, "pop with non-empty heap should succeed") 137 assert.Equal(t, p1, ps, "Wrong peer") 138 139 // Now update p2's pending to be higher than p3. 140 p2.pending = 10 141 h.update(p2.index) 142 143 popped := popAndVerifyHeap(t, &h) 144 assert.Equal(t, []*peerScore{p3, p2}, popped, "Unexpected order after p2 update") 145 } 146 147 func TestPeerHeapDelete(t *testing.T) { 148 const numPeers = 10 149 150 h := pendingHeap{nextRand: nextRand(0)} 151 peers := make([]*peerScore, numPeers) 152 for i := range peers { 153 peers[i] = &peerScore{pending: i} 154 h.pushPeer(peers[i]) 155 } 156 157 // The first peer is the lowest, remove it so it swaps with the last peer. 158 h.delete(peers[0]) 159 160 // Now when we pop peers, we expect peers 1 to N. 161 want := peers[1:] 162 popped := popAndVerifyHeap(t, &h) 163 assert.Equal(t, want, popped, "Unexpected peers after delete peer 0") 164 } 165 166 func (ph *pendingHeap) validate(ps *peerScore) error { 167 if ps.index < 0 || ps.index >= ph.Len() || ph.peers[ps.index] != ps { 168 return fmt.Errorf("pendingHeap bug: %+v has bad index %v (len %v)", ps, ps.index, ph.Len()) 169 } 170 return nil 171 } 172 173 func TestPeerHeapValidate(t *testing.T) { 174 h := pendingHeap{nextRand: nextRand(0)} 175 ps := &peerScore{pending: 1} 176 h.pushPeer(ps) 177 assert.Nil(t, h.validate(ps), "peer %v should validate", ps) 178 179 for _, i := range []int{0, -1, 5} { 180 ps := &peerScore{index: i} 181 assert.Error(t, h.validate(ps), "peer %v should not validate", ps) 182 } 183 } 184 185 func popAndVerifyHeap(t *testing.T, h *pendingHeap) []*peerScore { 186 var popped []*peerScore 187 188 lastScore := -1 189 for h.Len() > 0 { 190 verifyIndexes(t, h) 191 192 ps, ok := h.popPeer() 193 require.True(t, ok, "pop with non-empty heap should succeed") 194 popped = append(popped, ps) 195 196 if lastScore == -1 { 197 lastScore = ps.pending 198 continue 199 } 200 201 if ps.pending < lastScore { 202 t.Fatalf("heap returned peer %+v with fewer pending requests than %v", ps, lastScore) 203 } 204 lastScore = ps.pending 205 } 206 207 _, ok := h.popPeer() 208 require.False(t, ok, "Expected no peers to be returned with empty list") 209 return popped 210 } 211 212 func verifyIndexes(t *testing.T, h *pendingHeap) { 213 for i := range h.peers { 214 assert.Equal(t, i, h.peers[i].index, "wrong index for peer %v", h.peers[i]) 215 } 216 } 217 218 func TestPeerHeapInvalidAdd(t *testing.T) { 219 var ph pendingHeap 220 assert.Nil(t, (&ph).Add(nil, nil), "heap does not panic when adding nil") 221 } 222 223 func TestPeerHeapInvalidRemoval(t *testing.T) { 224 var ph pendingHeap 225 (&ph).Remove(nil, nil, nil) 226 } 227 228 func TestStaleSubscriberNoPanic(t *testing.T) { 229 ph := pendingHeap{nextRand: nextRandFromSlice([]int{0, 0})} 230 231 p1 := peertest.NewLightMockPeer(peertest.MockPeerIdentifier("p1"), peer.Available) 232 p2 := peertest.NewLightMockPeer(peertest.MockPeerIdentifier("p2"), peer.Available) 233 234 // this will place p1 at the end of the slice since it now has the largest 235 // request count 236 p1.StartRequest() 237 238 // add peers to heap 239 subscriber := ph.Add(p1, p1) 240 _ = ph.Add(p2, p2) 241 242 // remove p1 from the heap 243 ph.Remove(p1, p1, subscriber) 244 245 assert.NotPanics(t, func() { 246 // For on-going requests, it's possible to still have a reference to the 247 // subscriber, even if it is not present in the heap. 248 subscriber.UpdatePendingRequestCount(0) 249 }, "stale subscribers should not cause a panic") 250 } 251 252 func TestReleaseLockWithStaleSubscriber(t *testing.T) { 253 ph := pendingHeap{nextRand: nextRandFromSlice([]int{0})} 254 255 p1 := peertest.NewLightMockPeer(peertest.MockPeerIdentifier("p1"), peer.Available) 256 257 sub1 := ph.Add(p1, p1) 258 259 subscriber, ok := sub1.(*peerScore) 260 require.True(t, ok, "unexpected subscriber type") 261 262 // remove p1 from the heap, still holding onto a reference to the subscriber 263 ph.Remove(p1, p1, subscriber) 264 265 // simulate transport (eg HTTP) ending a call to a peer 266 ph.updatePendingRequestCount(subscriber, 0) 267 268 ctx, cancel := context.WithTimeout(context.Background(), 100*testtime.Millisecond) 269 270 go func() { 271 ph.Choose(&transport.Request{}) 272 cancel() 273 }() 274 275 <-ctx.Done() 276 assert.Equal(t, context.Canceled, ctx.Err(), "expected context to be canceled") 277 }