-
Notifications
You must be signed in to change notification settings - Fork 87
Add createConnectedBufferedChannels with simple delay model #1141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
{-# LANGUAGE FlexibleContexts #-} | ||
|
||
module ChannelTests (tests) where | ||
|
||
import Control.Monad.Class.MonadAsync | ||
import Control.Monad.Class.MonadTime | ||
import Control.Monad.IOSim | ||
|
||
import Network.TypedProtocol.Channel | ||
|
||
import Data.Time.Clock (diffTimeToPicoseconds, picosecondsToDiffTime) | ||
import System.Random | ||
|
||
import Test.QuickCheck | ||
import Test.Tasty (TestTree, testGroup) | ||
import Test.Tasty.QuickCheck (testProperty) | ||
|
||
|
||
tests :: TestTree | ||
tests = | ||
testGroup "Channel" | ||
[ testProperty "createConnectedDelayChannels" prop_createConnectedDelayChannels | ||
] | ||
|
||
|
||
prop_createConnectedDelayChannels | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be worthwhile to also test the other half of the |
||
:: Positive Int | ||
-> (Positive Int, Positive Int, Positive Int) | ||
-> Int | ||
-> [Int] -> Property | ||
prop_createConnectedDelayChannels | ||
(Positive maxsize) (Positive g_ms, Positive s_ms, Positive v_ms) | ||
seed xs = | ||
|
||
expectedDelayChannelTimes maxsize (g, s, v) prng xs | ||
=== actualDelayChannelTimes maxsize (g, s, v) prng xs | ||
where | ||
prng = mkStdGen seed | ||
|
||
g = millisecondsToDiffTime g_ms | ||
s = millisecondsToDiffTime s_ms | ||
v = millisecondsToDiffTime v_ms | ||
|
||
millisecondsToDiffTime :: Int -> DiffTime | ||
millisecondsToDiffTime = (/1e3) . fromIntegral | ||
|
||
actualDelayChannelTimes | ||
:: RandomGen g | ||
=> Int | ||
-> (DiffTime, DiffTime, DiffTime) | ||
-> g | ||
-> [b] | ||
-> [((VTime, b), (VTime, b))] | ||
actualDelayChannelTimes maxsize gsv prng xs0 = | ||
runSimOrThrow $ do | ||
(chanA, chanB) <- createConnectedBufferedChannels | ||
(const 1) maxsize gsv prng | ||
|
||
sa <- async (sender chanA xs0) | ||
ra <- async (receiver chanB [] (length xs0)) | ||
uncurry zip <$> waitBoth sa ra | ||
where | ||
sender chan xs = | ||
sequence | ||
[ do send chan x | ||
now <- getMonotonicTime | ||
return (now,x) | ||
| x <- xs ] | ||
|
||
receiver _ xs 0 = return (reverse xs) | ||
receiver chan xs n = do | ||
Just x <- recv chan | ||
now <- getMonotonicTime | ||
receiver chan ((now,x):xs) (n-1) | ||
|
||
|
||
expectedDelayChannelTimes | ||
:: RandomGen g | ||
=> Int | ||
-> (DiffTime, DiffTime, DiffTime) | ||
-> g | ||
-> [b] | ||
-> [((VTime, b), (VTime, b))] | ||
expectedDelayChannelTimes maxsize (g, s, v) prng0 xs0 = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's actually surprising to me that one can dequeue only when the channel is full to correctly model channels with delay. I think that's possible because the time (the Maybe you have a simple model how this works, it would be good to put it in haddock. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My "ah ha" moment along these lines was realizing that the scheduled "arrival times" of the elements in the buffer might not be monotonic -- so they're actually the "earliest possible arrival time" per message, not necessarily the actual arrival time. Hence That contention/overlap between the queue's semantics and the scheduled arrival times' values was a bit surprising. Would it be worthwhile to maintain the More directly to Marcin's comment: |
||
let (prngA, _prngB) = split prng0 | ||
vsamples :: [DiffTime] | ||
vsamples = map picosecondsToDiffTime | ||
(randomRs (0, diffTimeToPicoseconds v) prngA) | ||
in go (VTime 0) (VTime 0) (queue []) vsamples xs0 | ||
where | ||
|
||
go :: VTime -> VTime -> Q VTime -> [DiffTime] -> [b] -> [((VTime, b), (VTime, b))] | ||
go _ _ _ _ [] = [] | ||
|
||
go now maxarrive arrivals vsamples (x:xs) | ||
| queuelen arrivals == maxsize | ||
, Just (arrival, arrivals') <- dequeue arrivals | ||
= to (max now arrival) maxarrive arrivals' vsamples x xs | ||
|
||
| otherwise | ||
= to now maxarrive arrivals vsamples x xs | ||
|
||
to now maxarrive arrivals (vsample:vsamples) x xs = | ||
((depart, x), (arrive, x)) | ||
: go depart arrive (enqueue arrivals arrive) vsamples xs | ||
where | ||
depart = s `addTime` now | ||
arrive = max maxarrive ((g + vsample) `addTime` depart) | ||
-- cannot have the next message arrive before the last previous arrival | ||
|
||
to _ _ _ [] _ _ = error "expectedDelayChannelTimes: randomRs is infinite" | ||
|
||
|
||
|
||
---------------- | ||
-- Queue | ||
-- | ||
Comment on lines
+115
to
+117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This implementation is simple and good, but would reusing |
||
|
||
data Q a = Q [a] [a] | ||
|
||
queue :: [a] -> Q a | ||
queue xs = Q xs [] | ||
|
||
enqueue :: Q a -> a -> Q a | ||
enqueue (Q front back) x = Q front (x : back) | ||
|
||
dequeue :: Q a -> Maybe (a, Q a) | ||
dequeue (Q (x:xs) back) = Just (x, Q xs back) | ||
dequeue (Q [] back) = case reverse back of | ||
x:xs -> Just (x, Q xs []) | ||
[] -> Nothing | ||
|
||
queuelen :: Q a -> Int | ||
queuelen (Q front back) = length front + length back | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,7 +50,8 @@ library | |
io-sim-classes, | ||
bytestring, | ||
contra-tracer, | ||
time | ||
time, | ||
random | ||
Comment on lines
-53
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preserve alphabetic order? |
||
|
||
hs-source-dirs: src | ||
default-language: Haskell2010 | ||
|
@@ -80,6 +81,7 @@ test-suite test-protocols | |
, Network.TypedProtocol.ReqResp.Server | ||
, Network.TypedProtocol.ReqResp.Tests | ||
, Network.TypedProtocol.ReqResp.Type | ||
, ChannelTests | ||
build-depends: base | ||
, bytestring | ||
, contra-tracer | ||
|
@@ -89,6 +91,7 @@ test-suite test-protocols | |
, tasty | ||
, tasty-quickcheck | ||
, time | ||
, random | ||
Comment on lines
93
to
+94
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preserve alphabetic order? |
||
default-language: Haskell2010 | ||
ghc-options: -rtsopts | ||
-Wall | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not certain, but those three sentences together feel somewhat incorrect. Something like "the receiver is delayed until either G + S + V, or by something more than that because the channel had to preserve order instead of respecting that particular scheduled arrival time."
This is the same thing I mentioned in the test code.