Skip to content

Commit 474a7fc

Browse files
WFQ mux fairness test
The test has been enhanced to cover biased queuing
1 parent 57a1d7a commit 474a7fc

1 file changed

Lines changed: 54 additions & 20 deletions

File tree

network-mux/test/Test/Mux.hs

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
{-# LANGUAGE DataKinds #-}
44
{-# LANGUAGE FlexibleContexts #-}
55
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6+
{-# LANGUAGE LambdaCase #-}
67
{-# LANGUAGE NamedFieldPuns #-}
78
{-# LANGUAGE PackageImports #-}
89
{-# LANGUAGE RankNTypes #-}
@@ -29,7 +30,7 @@ import Data.Bits
2930
import Data.ByteString.Lazy qualified as BL
3031
import Data.ByteString.Lazy.Char8 qualified as BL8 (pack)
3132
import Data.Functor.Contravariant ((>$<))
32-
import Data.List (dropWhileEnd, nub)
33+
import Data.List (dropWhileEnd, group, nub)
3334
import Data.List qualified as List
3435
import Data.Map qualified as M
3536
import Data.Maybe (isNothing)
@@ -314,6 +315,35 @@ instance Arbitrary DummyCapability where
314315
]
315316

316317

318+
newtype MiniProtocolWeights = MiniProtocolWeights (MiniProtocolWeight, MiniProtocolWeight)
319+
deriving (Eq, Show)
320+
321+
instance Arbitrary MiniProtocolWeights where
322+
arbitrary = do
323+
wt1 <- arbitrary
324+
wt2 <- arbitrary
325+
let mkWt = MiniProtocolWeight
326+
if wt1 == wt2
327+
then pure $ MiniProtocolWeights (mkWt 1, mkWt 1)
328+
else pure $ MiniProtocolWeights (wt1, wt2)
329+
330+
shrink (MiniProtocolWeights (wt1, wt2)) =
331+
let mkWt = MiniProtocolWeight
332+
in MiniProtocolWeights (mkWt 1, mkWt 1)
333+
: [ MiniProtocolWeights (wt1', wt2')
334+
| wt1' <- shrink wt1
335+
, wt2' <- shrink wt2
336+
]
337+
338+
339+
newtype MiniProtocolWeight = MiniProtocolWeight Word8
340+
deriving (Eq, Show)
341+
342+
instance Arbitrary MiniProtocolWeight where
343+
arbitrary = MiniProtocolWeight <$> choose (1, 4)
344+
shrink (MiniProtocolWeight wt) = MiniProtocolWeight <$> filter (> 0) (shrink wt)
345+
346+
317347
-- | A pair of two bytestrings which lengths are unevenly distributed
318348
--
319349
data Uneven = Uneven DummyPayload DummyPayload
@@ -1008,8 +1038,10 @@ prop_mux_2_minis_Socket_buf cap a b = ioProperty $
10081038
-- The Mux bearer should alternate between sending data for the two responders.
10091039
--
10101040
prop_mux_starvation :: Uneven
1041+
-> MiniProtocolWeights
10111042
-> Property
1012-
prop_mux_starvation (Uneven response0 response1) =
1043+
prop_mux_starvation (Uneven response0 response1)
1044+
(MiniProtocolWeights (MiniProtocolWeight wt1, MiniProtocolWeight wt2)) =
10131045
let sduLen = Mx.SDUSize 1280 in
10141046
(BL.length (unDummyPayload response0) > 2 * fromIntegral (Mx.getSDUSize sduLen)) &&
10151047
(BL.length (unDummyPayload response1) > 2 * fromIntegral (Mx.getSDUSize sduLen)) ==>
@@ -1072,14 +1104,14 @@ prop_mux_starvation (Uneven response0 response1) =
10721104
miniProtocolDir = Mx.ResponderDirectionOnly,
10731105
miniProtocolLimits = defaultMiniProtocolLimits,
10741106
miniProtocolCapability = Nothing,
1075-
miniProtocolWeight = 1
1107+
miniProtocolWeight = wt1
10761108
}
10771109
serverApp3 = MiniProtocolInfo {
10781110
miniProtocolNum = Mx.MiniProtocolNum 3,
10791111
miniProtocolDir = Mx.ResponderDirectionOnly,
10801112
miniProtocolLimits = defaultMiniProtocolLimits,
10811113
miniProtocolCapability = Nothing,
1082-
miniProtocolWeight = 1
1114+
miniProtocolWeight = wt2
10831115
}
10841116

10851117
serverMux <- Mx.new serverTracer [serverApp2, serverApp3]
@@ -1119,28 +1151,30 @@ prop_mux_starvation (Uneven response0 response1) =
11191151

11201152
-- Then look at the message trace to check for starvation.
11211153
trace <- atomically $ readTVar traceHeaderVar
1122-
let es = map Mx.mhNum (take 100 (reverse trace))
1123-
ls = dropWhile (\e -> e == head es) es
1124-
fair = verifyStarvation ls
1154+
let es = map Mx.mhNum (take 100 (reverse trace))
1155+
-- We can't make 100% sure that both servers start responding at the same
1156+
-- time but once they are both up and running messages should alternate
1157+
-- between ReqResp2 and ReqResp3, so we drop the prefix of the protocol
1158+
-- which goes first, and trim the suffix when the first protocol finishes
1159+
ls = dropWhile (\e -> e == head es) es
1160+
ls' = dropWhileEnd (\e -> e == last ls) ls
1161+
fair = counterexample "muxer didn't interleave" (not . null $ ls')
1162+
.&&. label ("shrinkage " ++ labelPr_ ((length ls' * 100) `div` length es) ++ "%")
1163+
(verifyStarvation ls' (\case (Mx.MiniProtocolNum 2) -> wt1; _otherwise -> wt2))
11251164
return $ res_short .&&. res_long .&&. fair
11261165
where
1127-
-- We can't make 100% sure that both servers start responding at the same
1128-
-- time but once they are both up and running messages should alternate
1129-
-- between ReqResp2 and ReqResp3
1130-
verifyStarvation :: Eq a => [a] -> Property
1131-
verifyStarvation [] = property True
1132-
verifyStarvation ms =
1133-
let ms' = dropWhileEnd (\e -> e == last ms)
1134-
(head ms : dropWhile (\e -> e == head ms) ms)
1135-
++ [last ms]
1136-
in
1137-
label ("length " ++ labelPr_ ((length ms' * 100) `div` length ms) ++ "%")
1138-
$ label ("length " ++ label_ (length ms')) $ alternates ms'
1166+
verifyStarvation :: Eq a => [a] -> (a -> Word8) -> Property
1167+
verifyStarvation [] _atowt = property True
1168+
verifyStarvation ms atowt = label ("length " ++ label_ (length ms)) .
1169+
label ("groups " ++ label_ (length (group ms))) .
1170+
alternates $ group ms
11391171

11401172
where
11411173
alternates [] = True
11421174
alternates (_:[]) = True
1143-
alternates (a : b : as) = a /= b && alternates (b : as)
1175+
alternates (a : b : []) = length a <= fromIntegral (atowt (head a))
1176+
&& length b <= fromIntegral (atowt (head b))
1177+
alternates (a : b : as) = (length a == fromIntegral (atowt (head a))) && alternates (b : as)
11441178

11451179
label_ :: Int -> String
11461180
label_ n = mconcat

0 commit comments

Comments
 (0)