1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9 #include <list>
10 #include <map>
11 #include <thread>
12
13 #include <folly/Random.h>
14 #include <folly/io/async/test/MockTimeoutManager.h>
15 #include <folly/io/async/test/UndelayedDestruction.h>
16 #include <folly/portability/GTest.h>
17 #include <proxygen/lib/http/session/HTTP2PriorityQueue.h>
18
19 using namespace std::placeholders;
20 using namespace testing;
21 using folly::HHWheelTimer;
22 using folly::Random;
23 using folly::test::MockTimeoutManager;
24
25 namespace {
26 static char* fakeTxn = (char*)0xface0000;
27
28 const proxygen::HTTPCodec::StreamID kRootNodeId =
29 std::numeric_limits<uint64_t>::max();
30
makeFakeTxn(proxygen::HTTPCodec::StreamID id)31 proxygen::HTTPTransaction* makeFakeTxn(proxygen::HTTPCodec::StreamID id) {
32 return (proxygen::HTTPTransaction*)(fakeTxn + id);
33 }
34
getTxnID(proxygen::HTTPTransaction * txn)35 proxygen::HTTPCodec::StreamID getTxnID(proxygen::HTTPTransaction* txn) {
36 return (proxygen::HTTPCodec::StreamID)((char*)txn - fakeTxn);
37 }
38
39 // folly::Random::rand32 is broken because it takes RNG by value
rand32(uint32_t max,folly::Random::DefaultGenerator & rng)40 uint32_t rand32(uint32_t max, folly::Random::DefaultGenerator& rng) {
41 if (max == 0) {
42 return 0;
43 }
44
45 return std::uniform_int_distribution<uint32_t>(0, max - 1)(rng);
46 }
47
48 } // namespace
49
50 namespace proxygen {
51
52 using IDList = std::list<std::pair<HTTPCodec::StreamID, uint8_t>>;
53
54 class QueueTest : public testing::Test {
55 public:
QueueTest(HHWheelTimer * timer=nullptr)56 explicit QueueTest(HHWheelTimer* timer = nullptr)
57 : q_(WheelTimerInstance(timer), kRootNodeId) {
58 }
59
60 protected:
addTransaction(HTTPCodec::StreamID id,http2::PriorityUpdate pri,bool pnode=false,uint64_t * depth=nullptr)61 void addTransaction(HTTPCodec::StreamID id,
62 http2::PriorityUpdate pri,
63 bool pnode = false,
64 uint64_t* depth = nullptr) {
65 HTTP2PriorityQueue::Handle h = q_.addTransaction(
66 id, pri, pnode ? nullptr : makeFakeTxn(id), false, depth);
67 // Blow away the old handle. Hopefully the caller knows what they are doing
68 handles_.erase(id);
69 handles_.insert(std::make_pair(id, h));
70 if (!pnode) {
71 signalEgress(id, 1);
72 }
73 }
74
removeTransaction(HTTPCodec::StreamID id)75 void removeTransaction(HTTPCodec::StreamID id) {
76 q_.removeTransaction(handles_[id]);
77 }
78
updatePriority(HTTPCodec::StreamID id,http2::PriorityUpdate pri,uint64_t * depth=nullptr)79 void updatePriority(HTTPCodec::StreamID id,
80 http2::PriorityUpdate pri,
81 uint64_t* depth = nullptr) {
82 handles_[id] = q_.updatePriority(handles_[id], pri, depth);
83 }
84
signalEgress(HTTPCodec::StreamID id,bool mark)85 void signalEgress(HTTPCodec::StreamID id, bool mark) {
86 if (mark) {
87 q_.signalPendingEgress(handles_[id]);
88 } else {
89 q_.clearPendingEgress(handles_[id]);
90 }
91 }
92
buildSimpleTree()93 void buildSimpleTree() {
94 addTransaction(0, {kRootNodeId, false, 15});
95 addTransaction(3, {0, false, 3});
96 addTransaction(5, {0, false, 3});
97 addTransaction(7, {0, false, 7});
98 addTransaction(9, {5, false, 7});
99 }
100
visitNode(HTTP2PriorityQueue &,HTTPCodec::StreamID id,HTTPTransaction *,double r)101 bool visitNode(HTTP2PriorityQueue&,
102 HTTPCodec::StreamID id,
103 HTTPTransaction*,
104 double r) {
105 nodes_.push_back(std::make_pair(id, r * 100));
106 return false;
107 }
108
dump()109 void dump() {
110 nodes_.clear();
111 q_.iterate(
112 std::bind(&QueueTest::visitNode, this, std::ref(q_), _1, _2, _3),
113 [] { return false; },
114 true);
115 }
116
dumpBFS(const std::function<bool ()> & stopFn)117 void dumpBFS(const std::function<bool()>& stopFn) {
118 nodes_.clear();
119 q_.iterateBFS(
120 std::bind(&QueueTest::visitNode, this, _1, _2, _3, _4), stopFn, true);
121 }
122
nextEgress(bool spdyMode=false)123 void nextEgress(bool spdyMode = false) {
124 HTTP2PriorityQueue::NextEgressResult nextEgressResults;
125 q_.nextEgress(nextEgressResults, spdyMode);
126 nodes_.clear();
127 for (auto p : nextEgressResults) {
128 nodes_.push_back(std::make_pair(getTxnID(p.first), p.second * 100));
129 }
130 }
131
132 HTTP2PriorityQueue q_;
133 std::map<HTTPCodec::StreamID, HTTP2PriorityQueue::Handle> handles_;
134 IDList nodes_;
135 };
136
TEST_F(QueueTest,Basic)137 TEST_F(QueueTest, Basic) {
138 buildSimpleTree();
139 dump();
140 EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 25}, {5, 25}, {9, 100}, {7, 50}}));
141
142 // Add another node, make sure we get the correct depth.
143 uint64_t depth;
144 addTransaction(11, {7, false, 15}, false, &depth);
145 EXPECT_EQ(depth, 3);
146 }
147
TEST_F(QueueTest,RemoveLeaf)148 TEST_F(QueueTest, RemoveLeaf) {
149 buildSimpleTree();
150
151 removeTransaction(3);
152 dump();
153
154 EXPECT_EQ(nodes_, IDList({{0, 100}, {5, 33}, {9, 100}, {7, 66}}));
155 }
156
TEST_F(QueueTest,RemoveParent)157 TEST_F(QueueTest, RemoveParent) {
158 buildSimpleTree();
159
160 removeTransaction(5);
161 dump();
162
163 EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 25}, {7, 50}, {9, 25}}));
164 }
165
TEST_F(QueueTest,RemoveParentWeights)166 TEST_F(QueueTest, RemoveParentWeights) {
167 // weight_ / totalChildWeight_ < 1
168 addTransaction(0, {kRootNodeId, false, 0});
169 addTransaction(3, {0, false, 255});
170 addTransaction(5, {0, false, 255});
171
172 removeTransaction(0);
173 dump();
174
175 EXPECT_EQ(nodes_, IDList({{3, 50}, {5, 50}}));
176 }
177
TEST_F(QueueTest,NodeDepth)178 TEST_F(QueueTest, NodeDepth) {
179 uint64_t depth{33}; // initialize to some wrong value
180 addTransaction(0, {kRootNodeId, false, 15}, false, &depth);
181 EXPECT_EQ(depth, 1);
182
183 addTransaction(3, {0, false, 3}, false, &depth);
184 EXPECT_EQ(depth, 2);
185
186 addTransaction(5, {3, true, 7}, false, &depth);
187 EXPECT_EQ(depth, 3);
188
189 addTransaction(9, {0, false, 3}, true, &depth);
190 EXPECT_EQ(depth, 2);
191 EXPECT_EQ(q_.numPendingEgress(), 3);
192 EXPECT_EQ(q_.numVirtualNodes(), 1);
193
194 depth = 55; // some unlikely depth
195 addTransaction(9, {0, false, 31}, false, &depth);
196 EXPECT_EQ(depth, 2);
197 EXPECT_EQ(q_.numPendingEgress(), 4);
198 EXPECT_EQ(q_.numVirtualNodes(), 0);
199
200 addTransaction(11, {0, true, 7}, false, &depth);
201 EXPECT_EQ(depth, 2);
202 EXPECT_EQ(q_.numPendingEgress(), 5);
203 EXPECT_EQ(q_.numVirtualNodes(), 0);
204
205 addTransaction(13, {kRootNodeId, true, 23}, true, &depth);
206 EXPECT_EQ(depth, 1);
207 EXPECT_EQ(q_.numPendingEgress(), 5);
208 EXPECT_EQ(q_.numVirtualNodes(), 1);
209
210 depth = 77; // some unlikely depth
211 addTransaction(13, {kRootNodeId, true, 33}, false, &depth);
212 EXPECT_EQ(depth, 1);
213 EXPECT_EQ(q_.numPendingEgress(), 6);
214 EXPECT_EQ(q_.numVirtualNodes(), 0);
215 }
216
TEST_F(QueueTest,UpdateWeight)217 TEST_F(QueueTest, UpdateWeight) {
218 buildSimpleTree();
219
220 uint64_t depth = 0;
221 updatePriority(5, {0, false, 7}, &depth);
222 dump();
223
224 EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 20}, {5, 40}, {9, 100}, {7, 40}}));
225 EXPECT_EQ(depth, 2);
226 }
227
228 // Previously the code would allow duplicate entries in the priority tree under
229 // certain circumstances.
TEST_F(QueueTest,DuplicateID)230 TEST_F(QueueTest, DuplicateID) {
231 q_.addOrUpdatePriorityNode(0, {kRootNodeId, false, 15});
232 addTransaction(0, {kRootNodeId, true, 15});
233 q_.addOrUpdatePriorityNode(3, {0, false, 15});
234 addTransaction(5, {3, false, 15});
235 addTransaction(3, {5, false, 15});
236 removeTransaction(5);
237 auto stopFn = [] { return false; };
238
239 dumpBFS(stopFn);
240 EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 100}}));
241 }
242
TEST_F(QueueTest,UpdateWeightNotEnqueued)243 TEST_F(QueueTest, UpdateWeightNotEnqueued) {
244 addTransaction(0, {kRootNodeId, false, 7});
245 addTransaction(3, {0, false, 7});
246
247 signalEgress(0, false);
248 signalEgress(3, false);
249 uint64_t depth = 0;
250 updatePriority(0, {3, false, 7}, &depth);
251 dump();
252
253 EXPECT_EQ(nodes_, IDList({{3, 100}, {0, 100}}));
254 EXPECT_EQ(depth, 2);
255 }
256
TEST_F(QueueTest,UpdateWeightExcl)257 TEST_F(QueueTest, UpdateWeightExcl) {
258 buildSimpleTree();
259
260 updatePriority(5, {0, true, 7});
261 dump();
262
263 EXPECT_EQ(nodes_, IDList({{0, 100}, {5, 100}, {9, 40}, {3, 20}, {7, 40}}));
264 signalEgress(0, false);
265 nextEgress();
266 EXPECT_EQ(nodes_, IDList({{5, 100}}));
267 }
268
TEST_F(QueueTest,UpdateWeightExclDequeued)269 TEST_F(QueueTest, UpdateWeightExclDequeued) {
270 buildSimpleTree();
271
272 signalEgress(5, false);
273 updatePriority(5, {0, true, 7});
274 signalEgress(0, false);
275 nextEgress();
276
277 EXPECT_EQ(nodes_, IDList({{9, 40}, {7, 40}, {3, 20}}));
278 }
279
TEST_F(QueueTest,UpdateWeightUnknownParent)280 TEST_F(QueueTest, UpdateWeightUnknownParent) {
281 buildSimpleTree();
282
283 uint64_t depth = 0;
284 updatePriority(5, {97, false, 15}, &depth);
285 dump();
286
287 EXPECT_EQ(nodes_,
288 IDList({{0, 50}, {3, 33}, {7, 66}, {97, 50}, {5, 100}, {9, 100}}));
289 EXPECT_EQ(depth, 2);
290
291 depth = 0;
292 updatePriority(9, {99, false, 15}, &depth);
293 dump();
294
295 EXPECT_EQ(
296 nodes_,
297 IDList(
298 {{0, 33}, {3, 33}, {7, 66}, {97, 33}, {5, 100}, {99, 33}, {9, 100}}));
299 EXPECT_EQ(depth, 2);
300 }
301
TEST_F(QueueTest,UpdateParentSibling)302 TEST_F(QueueTest, UpdateParentSibling) {
303 buildSimpleTree();
304
305 updatePriority(5, {3, false, 3});
306 dump();
307
308 EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 33}, {5, 100}, {9, 100}, {7, 66}}));
309 signalEgress(0, false);
310 nextEgress();
311 EXPECT_EQ(nodes_, IDList({{7, 66}, {3, 33}}));
312
313 // Clear 5's egress (so it is only in the tree because 9 has egress) and move
314 // it back. Hit's a slightly different code path in reparent
315 signalEgress(5, false);
316 updatePriority(5, {0, false, 3});
317 dump();
318
319 EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 25}, {7, 50}, {5, 25}, {9, 100}}));
320
321 nextEgress();
322 EXPECT_EQ(nodes_, IDList({{7, 50}, {3, 25}, {9, 25}}));
323 }
324
TEST_F(QueueTest,UpdateParentSiblingExcl)325 TEST_F(QueueTest, UpdateParentSiblingExcl) {
326 buildSimpleTree();
327
328 updatePriority(7, {5, true, 3});
329 dump();
330
331 EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 50}, {5, 50}, {7, 100}, {9, 100}}));
332 signalEgress(0, false);
333 signalEgress(3, false);
334 signalEgress(5, false);
335 nextEgress();
336 EXPECT_EQ(nodes_, IDList({{7, 100}}));
337 }
338
TEST_F(QueueTest,UpdateParentAncestor)339 TEST_F(QueueTest, UpdateParentAncestor) {
340 buildSimpleTree();
341
342 updatePriority(9, {kRootNodeId, false, 15});
343 dump();
344
345 EXPECT_EQ(nodes_, IDList({{0, 50}, {3, 25}, {5, 25}, {7, 50}, {9, 50}}));
346 nextEgress();
347 EXPECT_EQ(nodes_, IDList({{0, 50}, {9, 50}}));
348 }
349
TEST_F(QueueTest,UpdateParentAncestorExcl)350 TEST_F(QueueTest, UpdateParentAncestorExcl) {
351 buildSimpleTree();
352
353 updatePriority(9, {kRootNodeId, true, 15});
354 dump();
355
356 EXPECT_EQ(nodes_, IDList({{9, 100}, {0, 100}, {3, 25}, {5, 25}, {7, 50}}));
357 nextEgress();
358 EXPECT_EQ(nodes_, IDList({{9, 100}}));
359 }
360
TEST_F(QueueTest,UpdateParentDescendant)361 TEST_F(QueueTest, UpdateParentDescendant) {
362 buildSimpleTree();
363
364 updatePriority(0, {5, false, 7});
365 dump();
366
367 EXPECT_EQ(nodes_, IDList({{5, 100}, {9, 50}, {0, 50}, {3, 33}, {7, 66}}));
368 nextEgress();
369 EXPECT_EQ(nodes_, IDList({{5, 100}}));
370 signalEgress(5, false);
371 nextEgress();
372 EXPECT_EQ(nodes_, IDList({{9, 50}, {0, 50}}));
373 }
374
TEST_F(QueueTest,UpdateParentDescendantExcl)375 TEST_F(QueueTest, UpdateParentDescendantExcl) {
376 buildSimpleTree();
377
378 updatePriority(0, {5, true, 7});
379 dump();
380
381 EXPECT_EQ(nodes_, IDList({{5, 100}, {0, 100}, {3, 20}, {7, 40}, {9, 40}}));
382 nextEgress();
383 EXPECT_EQ(nodes_, IDList({{5, 100}}));
384 signalEgress(5, false);
385 signalEgress(0, false);
386 nextEgress();
387 EXPECT_EQ(nodes_, IDList({{7, 40}, {9, 40}, {3, 20}}));
388 }
389
TEST_F(QueueTest,ExclusiveAdd)390 TEST_F(QueueTest, ExclusiveAdd) {
391 buildSimpleTree();
392
393 addTransaction(11, {0, true, 100});
394
395 dump();
396 EXPECT_EQ(nodes_,
397 IDList({{0, 100}, {11, 100}, {3, 25}, {5, 25}, {9, 100}, {7, 50}}));
398 }
399
TEST_F(QueueTest,AddUnknown)400 TEST_F(QueueTest, AddUnknown) {
401 buildSimpleTree();
402
403 addTransaction(11, {75, false, 15});
404
405 dump();
406 EXPECT_EQ(
407 nodes_,
408 IDList(
409 {{0, 50}, {3, 25}, {5, 25}, {9, 100}, {7, 50}, {75, 50}, {11, 100}}));
410
411 // Now let's add the missing parent node and check if it was
412 // relocated properly
413 addTransaction(75, {0, false, 7});
414
415 dump();
416 EXPECT_EQ(nodes_,
417 IDList({{0, 100},
418 {3, 16},
419 {5, 16},
420 {9, 100},
421 {7, 33},
422 {75, 33},
423 {11, 100}}));
424 }
425
TEST_F(QueueTest,AddMax)426 TEST_F(QueueTest, AddMax) {
427 addTransaction(0, {kRootNodeId, false, 255});
428
429 nextEgress();
430 EXPECT_EQ(nodes_, IDList({{0, 100}}));
431 }
432
TEST_F(QueueTest,Misc)433 TEST_F(QueueTest, Misc) {
434 buildSimpleTree();
435
436 EXPECT_FALSE(q_.empty());
437 EXPECT_EQ(q_.numPendingEgress(), 5);
438 signalEgress(0, false);
439 EXPECT_EQ(q_.numPendingEgress(), 4);
440 EXPECT_FALSE(q_.empty());
441 removeTransaction(9);
442 removeTransaction(0);
443 dump();
444 EXPECT_EQ(nodes_, IDList({{3, 25}, {5, 25}, {7, 50}}));
445 }
446
TEST_F(QueueTest,IterateBFS)447 TEST_F(QueueTest, IterateBFS) {
448 buildSimpleTree();
449
450 auto stopFn = [this] { return nodes_.size() > 2; };
451
452 dumpBFS(stopFn);
453 EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 25}, {5, 25}, {7, 50}}));
454 }
455
TEST_F(QueueTest,NextEgress)456 TEST_F(QueueTest, NextEgress) {
457 buildSimpleTree();
458
459 nextEgress();
460 EXPECT_EQ(nodes_, IDList({{0, 100}}));
461
462 addTransaction(11, {7, false, 15});
463 signalEgress(0, false);
464
465 nextEgress();
466 EXPECT_EQ(nodes_, IDList({{7, 50}, {3, 25}, {5, 25}}));
467
468 signalEgress(5, false);
469 nextEgress();
470 EXPECT_EQ(nodes_, IDList({{7, 50}, {3, 25}, {9, 25}}));
471 signalEgress(5, true);
472
473 signalEgress(3, false);
474 nextEgress();
475 EXPECT_EQ(nodes_, IDList({{7, 66}, {5, 33}}));
476
477 signalEgress(5, false);
478 nextEgress();
479 EXPECT_EQ(nodes_, IDList({{7, 66}, {9, 33}}));
480
481 signalEgress(7, false);
482 nextEgress();
483 EXPECT_EQ(nodes_, IDList({{11, 66}, {9, 33}}));
484
485 signalEgress(9, false);
486 nextEgress();
487 EXPECT_EQ(nodes_, IDList({{11, 100}}));
488
489 signalEgress(3, true);
490 signalEgress(7, true);
491 signalEgress(9, true);
492 nextEgress();
493 EXPECT_EQ(nodes_, IDList({{7, 50}, {3, 25}, {9, 25}}));
494 }
495
TEST_F(QueueTest,NextEgressExclusiveAdd)496 TEST_F(QueueTest, NextEgressExclusiveAdd) {
497 buildSimpleTree();
498
499 // clear all egress
500 signalEgress(0, false);
501 signalEgress(3, false);
502 signalEgress(5, false);
503 signalEgress(7, false);
504 signalEgress(9, false);
505
506 // Add a transaction with exclusive dependency, clear its egress
507 addTransaction(11, {0, true, 100});
508 signalEgress(11, false);
509
510 // signal egress for a child that got moved via exclusive dep
511 signalEgress(3, true);
512 nextEgress();
513 EXPECT_EQ(nodes_, IDList({{3, 100}}));
514 EXPECT_EQ(q_.numPendingEgress(), 1);
515 }
516
TEST_F(QueueTest,NextEgressExclusiveAddWithEgress)517 TEST_F(QueueTest, NextEgressExclusiveAddWithEgress) {
518 buildSimpleTree();
519
520 // clear all egress, except 3
521 signalEgress(0, false);
522 signalEgress(5, false);
523 signalEgress(7, false);
524 signalEgress(9, false);
525
526 // Add a transaction with exclusive dependency, clear its egress
527 addTransaction(11, {0, true, 100});
528 signalEgress(11, false);
529 nextEgress();
530 EXPECT_EQ(nodes_, IDList({{3, 100}}));
531 EXPECT_EQ(q_.numPendingEgress(), 1);
532 }
533
TEST_F(QueueTest,UpdatePriorityReparentSubtree)534 TEST_F(QueueTest, UpdatePriorityReparentSubtree) {
535 buildSimpleTree();
536
537 // clear all egress, except 9
538 signalEgress(0, false);
539 signalEgress(3, false);
540 signalEgress(5, false);
541 signalEgress(7, false);
542
543 // Update priority of non-enqueued but in egress tree node
544 updatePriority(5, {0, false, 14}, nullptr);
545
546 // update 9's weight and reparent
547 updatePriority(9, {3, false, 14}, nullptr);
548
549 nextEgress();
550 EXPECT_EQ(nodes_, IDList({{9, 100}}));
551 }
552
TEST_F(QueueTest,NextEgressRemoveParent)553 TEST_F(QueueTest, NextEgressRemoveParent) {
554 buildSimpleTree();
555
556 // Clear egress for all except txn=9
557 signalEgress(0, false);
558 signalEgress(3, false);
559 signalEgress(5, false);
560 signalEgress(7, false);
561
562 // Remove parent of 9 (5)
563 removeTransaction(5);
564 nextEgress();
565 EXPECT_EQ(nodes_, IDList({{9, 100}}));
566
567 // signal egress for 9's new siblings to verify weights
568 signalEgress(3, true);
569 signalEgress(7, true);
570
571 nextEgress();
572 EXPECT_EQ(nodes_, IDList({{7, 50}, {9, 25}, {3, 25}}));
573 }
574
TEST_F(QueueTest,AddExclusiveDescendantEnqueued)575 TEST_F(QueueTest, AddExclusiveDescendantEnqueued) {
576 addTransaction(0, {kRootNodeId, false, 100});
577 addTransaction(3, {0, false, 100});
578 addTransaction(5, {3, false, 100});
579 signalEgress(0, false);
580 signalEgress(3, false);
581 // add a new exclusive child of 1. 1's child 3 is not enqueued but is in the
582 // the egress tree.
583 addTransaction(7, {0, true, 100});
584 nextEgress();
585 EXPECT_EQ(nodes_, IDList({{7, 100}}));
586 }
587
TEST_F(QueueTest,NextEgressRemoveParentEnqueued)588 TEST_F(QueueTest, NextEgressRemoveParentEnqueued) {
589 addTransaction(0, {kRootNodeId, false, 100});
590 addTransaction(3, {0, false, 100});
591 addTransaction(5, {3, false, 100});
592 signalEgress(3, false);
593 // When 3's children (5) are added to 1, both are already in the egress tree
594 // and the signal does not need to propagate
595 removeTransaction(3);
596 signalEgress(0, false);
597 nextEgress();
598 EXPECT_EQ(nodes_, IDList({{5, 100}}));
599 }
600
TEST_F(QueueTest,NextEgressRemoveParentEnqueuedIndirect)601 TEST_F(QueueTest, NextEgressRemoveParentEnqueuedIndirect) {
602 addTransaction(0, {kRootNodeId, false, 100});
603 addTransaction(3, {0, false, 100});
604 addTransaction(5, {3, false, 100});
605 addTransaction(7, {0, false, 100});
606 signalEgress(3, false);
607 signalEgress(0, false);
608 // When 3's children (5) are added to 1, both are already in the egress tree
609 // and the signal does not need to propagate
610 removeTransaction(3);
611 nextEgress();
612 EXPECT_EQ(nodes_, IDList({{7, 50}, {5, 50}}));
613 }
614
TEST_F(QueueTest,ChromeTest)615 TEST_F(QueueTest, ChromeTest) {
616 // Tries to simulate Chrome's current behavior by performing pseudo-random
617 // add-exclusive, signal, clear and remove with 3 insertion points
618 // (hi,mid,low). Note the test uses rand32() with a particular seed so the
619 // output is predictable.
620 HTTPCodec::StreamID pris[3] = {0, 3, 5};
621 addTransaction(0, {kRootNodeId, true, 99});
622 signalEgress(0, false);
623 addTransaction(3, {0, true, 99});
624 signalEgress(3, false);
625 addTransaction(5, {3, true, 99});
626 signalEgress(5, false);
627
628 std::vector<HTTPCodec::StreamID> txns;
629 std::vector<HTTPCodec::StreamID> active;
630 std::vector<HTTPCodec::StreamID> inactive;
631 HTTPCodec::StreamID txn = 0;
632 uint64_t idx = 0;
633 HTTPCodec::StreamID nextId = 7;
634 auto gen = Random::create();
635 gen.seed(12345); // luggage combo
636 for (auto i = 4; i < 1000; i++) {
637 uint8_t action = rand32(4, gen);
638 if (action == 0) {
639 // add exclusive on pseudo-random priority anchor
640 uint8_t pri = rand32(3, gen);
641 HTTPCodec::StreamID dep = pris[pri];
642 txn = nextId;
643 nextId += 2;
644 VLOG(2) << "Adding txn=" << txn << " with dep=" << dep;
645 addTransaction(txn, {(uint32_t)dep, true, 99});
646 txns.push_back(txn);
647 active.push_back(txn);
648 } else if (action == 1 && !inactive.empty()) {
649 // signal an inactive txn
650 idx = rand32(inactive.size(), gen);
651 txn = inactive[idx];
652 VLOG(2) << "Activating txn=" << txn;
653 signalEgress(txn, true);
654 inactive.erase(inactive.begin() + idx);
655 active.push_back(txn);
656 } else if (action == 2 && !active.empty()) {
657 // clear an active transaction
658 idx = rand32(active.size(), gen);
659 txn = active[idx];
660 VLOG(2) << "Deactivating txn=" << txn;
661 signalEgress(txn, false);
662 active.erase(active.begin() + idx);
663 inactive.push_back(txn);
664 } else if (action == 3 && !txns.empty()) {
665 // remove a transaction
666 idx = rand32(txns.size(), gen);
667 txn = txns[idx];
668 VLOG(2) << "Removing txn=" << txn;
669 removeTransaction(txn);
670 txns.erase(txns.begin() + idx);
671 auto it = std::find(active.begin(), active.end(), txn);
672 if (it != active.end()) {
673 active.erase(it);
674 }
675 it = std::find(inactive.begin(), inactive.end(), txn);
676 if (it != inactive.end()) {
677 inactive.erase(it);
678 }
679 }
680 VLOG(2) << "Active nodes=" << q_.numPendingEgress();
681 if (!q_.empty()) {
682 nextEgress();
683 EXPECT_GT(nodes_.size(), 0);
684 }
685 }
686 }
687
TEST_F(QueueTest,NextEgressSpdy)688 TEST_F(QueueTest, NextEgressSpdy) {
689 // 0 and 3 are vnodes representing pri 0 and 1
690 addTransaction(0, {kRootNodeId, false, 0}, true);
691 addTransaction(3, {0, false, 0}, true);
692
693 // 7 and 9 are pri 0, 11 and 13 are pri 1
694 addTransaction(7, {0, false, 15});
695 addTransaction(9, {0, false, 15});
696 addTransaction(11, {3, false, 15});
697 addTransaction(13, {3, false, 15});
698
699 nextEgress(true);
700 EXPECT_EQ(nodes_, IDList({{7, 50}, {9, 50}}));
701
702 signalEgress(7, false);
703 nextEgress(true);
704 EXPECT_EQ(nodes_, IDList({{9, 100}}));
705
706 signalEgress(9, false);
707 nextEgress(true);
708 EXPECT_EQ(nodes_, IDList({{11, 50}, {13, 50}}));
709 }
710
TEST_F(QueueTest,AddOrUpdate)711 TEST_F(QueueTest, AddOrUpdate) {
712 q_.addOrUpdatePriorityNode(0, {kRootNodeId, false, 15});
713 q_.addOrUpdatePriorityNode(3, {kRootNodeId, false, 15});
714 dump();
715 EXPECT_EQ(nodes_, IDList({{0, 50}, {3, 50}}));
716 q_.addOrUpdatePriorityNode(0, {kRootNodeId, false, 3});
717 dump();
718 EXPECT_EQ(nodes_, IDList({{0, 20}, {3, 80}}));
719 }
720
721 class DanglingQueueTestBase {
722 public:
DanglingQueueTestBase()723 DanglingQueueTestBase() {
724 // Just under two ticks
725 HTTP2PriorityQueue::setNodeLifetime(
726 std::chrono::milliseconds(2 * HHWheelTimer::DEFAULT_TICK_INTERVAL - 1));
727 EXPECT_CALL(timeoutManager_, scheduleTimeout(_, _))
728 .WillRepeatedly(Invoke(
729 [this](folly::AsyncTimeout* timeout, std::chrono::milliseconds) {
730 timeouts_.push_back(timeout);
731 return true;
732 }));
733 }
734
735 protected:
expireNodes()736 void expireNodes() {
737 std::this_thread::sleep_for(
738 std::chrono::milliseconds(2 * HHWheelTimer::DEFAULT_TICK_INTERVAL));
739 // Node lifetime it just under two ticks, so firing twice expires all nodes
740 tick();
741 tick();
742 }
743
tick()744 void tick() {
745 std::list<folly::AsyncTimeout*> timeouts;
746 std::swap(timeouts_, timeouts);
747 for (auto timeout : timeouts) {
748 timeout->timeoutExpired();
749 }
750 }
751
752 std::list<folly::AsyncTimeout*> timeouts_;
753 testing::NiceMock<MockTimeoutManager> timeoutManager_;
754 folly::UndelayedDestruction<HHWheelTimer> timer_{&timeoutManager_};
755 };
756
757 // Order declaration of the base classes for this fixture matters here: we want
758 // to pass the timer initialized as part of DanglingQueueTest into to QueueTest,
759 // so it must be initialized first.
760 class DanglingQueueTest
761 : public DanglingQueueTestBase
762 , public QueueTest {
763 public:
DanglingQueueTest()764 DanglingQueueTest() : DanglingQueueTestBase(), QueueTest(&timer_) {
765 }
766 };
767
TEST_F(DanglingQueueTest,Basic)768 TEST_F(DanglingQueueTest, Basic) {
769 addTransaction(0, {kRootNodeId, false, 15});
770 removeTransaction(0);
771 dump();
772 EXPECT_EQ(nodes_, IDList({{0, 100}}));
773 expireNodes();
774 dump();
775 EXPECT_EQ(nodes_, IDList({}));
776 }
777
TEST_F(DanglingQueueTest,Chain)778 TEST_F(DanglingQueueTest, Chain) {
779 addTransaction(0, {kRootNodeId, false, 15}, true);
780 addTransaction(3, {0, false, 15}, true);
781 addTransaction(5, {3, false, 15}, true);
782 dump();
783 EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 100}, {5, 100}}));
784 expireNodes();
785 dump();
786 EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 100}}));
787 expireNodes();
788 dump();
789 EXPECT_EQ(nodes_, IDList({{0, 100}}));
790 expireNodes();
791 dump();
792 EXPECT_EQ(nodes_, IDList({}));
793 }
794
TEST_F(DanglingQueueTest,Drop)795 TEST_F(DanglingQueueTest, Drop) {
796 addTransaction(0, {kRootNodeId, false, 15}, true);
797 addTransaction(3, {0, false, 15}, true);
798 addTransaction(5, {0, false, 15}, true);
799 dump();
800 q_.dropPriorityNodes();
801 dump();
802 EXPECT_EQ(nodes_, IDList({}));
803 }
804
TEST_F(DanglingQueueTest,ExpireParentOfMismatchedTwins)805 TEST_F(DanglingQueueTest, ExpireParentOfMismatchedTwins) {
806 addTransaction(0, {kRootNodeId, true, 219}, false);
807 addTransaction(3, {0, false, 146}, false);
808 addTransaction(5, {0, false, 146}, false);
809 signalEgress(3, false);
810 signalEgress(5, true);
811 removeTransaction(0);
812 dump();
813 tick();
814 expireNodes();
815 dump();
816 EXPECT_EQ(nodes_, IDList({{3, 50}, {5, 50}}));
817 }
818
TEST_F(DanglingQueueTest,AddExpireAdd)819 TEST_F(DanglingQueueTest, AddExpireAdd) {
820 // Add a virtual node
821 addTransaction(0, {kRootNodeId, true, 219}, true);
822 // expire it
823 expireNodes();
824 dump();
825 EXPECT_TRUE(q_.empty());
826 // Add a real node with the same id
827 addTransaction(0, {kRootNodeId, true, 219}, false);
828 dump();
829 EXPECT_EQ(nodes_, IDList({{0, 100}}));
830 }
831
832 class DummyTimeout : public HHWheelTimer::Callback {
timeoutExpired()833 void timeoutExpired() noexcept override {
834 }
835 };
836
TEST_F(DanglingQueueTest,Refresh)837 TEST_F(DanglingQueueTest, Refresh) {
838 // Having a long running timeout prevents HHWheelTimer::Callback::setScheduled
839 // from checking the real time
840 DummyTimeout t;
841 timer_.scheduleTimeout(&t, std::chrono::seconds(300));
842 addTransaction(0, {kRootNodeId, false, 15});
843 addTransaction(3, {kRootNodeId, false, 15});
844 // 0 is now virtual
845 removeTransaction(0);
846 dump();
847 EXPECT_EQ(nodes_, IDList({{0, 50}, {3, 50}}));
848 tick();
849 // before 0 times out, change it's priority, should still be there
850 updatePriority(0, {kRootNodeId, false, 3});
851 dump();
852 EXPECT_EQ(nodes_, IDList({{0, 20}, {3, 80}}));
853
854 tick();
855 dump();
856 EXPECT_EQ(nodes_, IDList({{0, 20}, {3, 80}}));
857 expireNodes();
858 dump();
859 EXPECT_EQ(nodes_, IDList({{3, 100}}));
860 }
861
TEST_F(DanglingQueueTest,Max)862 TEST_F(DanglingQueueTest, Max) {
863 buildSimpleTree();
864 q_.setMaxVirtualNodes(3);
865 for (auto i = 1; i <= 9; i += 2) {
866 removeTransaction(i == 1 ? 0 : i);
867 }
868 dump();
869 EXPECT_EQ(nodes_, IDList({{0, 100}, {3, 50}, {5, 50}}));
870 // 0 expires first and it re-weights 3 and 5, which extends their lifetime
871 expireNodes();
872 dump();
873 EXPECT_EQ(nodes_, IDList({{3, 50}, {5, 50}}));
874 expireNodes();
875 dump();
876 EXPECT_EQ(nodes_, IDList());
877 }
878
TEST_F(QueueTest,Rebuild)879 TEST_F(QueueTest, Rebuild) {
880 buildSimpleTree();
881 q_.rebuildTree();
882 dump();
883 EXPECT_EQ(nodes_, IDList({{3, 20}, {9, 20}, {5, 20}, {7, 20}, {0, 20}}));
884 }
885
886 } // namespace proxygen
887