hans-2.6.0.0: src/Hans/Layer/Tcp/Timers.hs
{-# LANGUAGE RecordWildCards #-}
module Hans.Layer.Tcp.Timers (
initTimers
, resetIdle
, whenIdleFor
, set2MSL
, calibrateRTO
) where
import Hans.Channel
import Hans.Layer.Tcp.Messages
import Hans.Layer.Tcp.Monad
import Hans.Layer.Tcp.Types
import Hans.Layer.Tcp.Window
import Control.Concurrent (forkIO,threadDelay)
import Control.Monad (when,unless,forever,void)
import Data.Time.Clock.POSIX (POSIXTime)
import qualified Data.Foldable as F
-- Timer Handlers --------------------------------------------------------------
-- | Schedule the timers to run on the fast and slow intervals.
initTimers :: TcpHandle -> IO ()
initTimers tcp = do
every 500 slowTimer
every 200 fastTimer
where
-- XXX we should investigate timing the time that body takes to run, and
-- making the decision about how long to delay based on that.
every n body = void $ forkIO $ forever $
do threadDelay timeout
send tcp body
where
timeout = n * 1000
-- | Fires every 500ms.
slowTimer :: Tcp ()
slowTimer =
do -- first, handle active connections
eachConnection $ do
-- the slow timer is valid for all states but TimeWait; the check is done
-- here because the socket may have only just transitioned to TimeWait,
-- and hasn't been moved to hostTimeWaits yet.
TcpSocket { .. } <- getTcpSocket
unless (tcpState == TimeWait) handleRTO
handle2MSL
updateTimers
-- second, decrement the 2MSL timer for sockets in the TimeWait state
modifyHost_ $ \ host ->
host { hostTimeWaits = stepTimeWaitConnections (hostTimeWaits host) }
resetIdle :: Sock ()
resetIdle = modifyTcpTimers_ (\tt -> tt { ttIdle = 0 })
-- | Handle only the delayed ack timer, fires ever 200ms.
fastTimer :: Tcp ()
fastTimer = eachConnection $ do
tcp <- getTcpSocket
when (tcpState tcp /= TimeWait && needsDelayedAck tcp) ack
-- Timer Interaction -----------------------------------------------------------
-- | Update timers, decrementing to 0 ones that expire, and incrementing the
-- others.
updateTimers :: Sock ()
updateTimers =
modifyTcpSocket_ $ \ tcp ->
let tt = tcpTimers tcp
in tcp { tcpTimers = tt
{ tt2MSL = decrement (tt2MSL tt)
, ttIdle = increment (ttIdle tt)
}
}
where
decrement 0 = 0
decrement val = pred val
increment = succ
-- | Conditionally run a timer, when this slow-timer tick will decrement it to
-- zero.
whenTimer :: (TcpTimers -> SlowTicks) -> Sock () -> Sock ()
whenTimer prj body = do
tt <- getTcpTimers
when (prj tt == 1) body
-- | Conditionally run an action when the connection has been idle for at least
-- timeout @SlowTick@s.
whenIdleFor :: Int -> Sock () -> Sock ()
whenIdleFor timeout body = do
tt <- getTcpTimers
when (ttIdle tt >= timeout) body
-- 2MSL ------------------------------------------------------------------------
-- | Set the value of the 2MSL timer.
set2MSL :: SlowTicks -> Sock ()
set2MSL val = modifyTcpTimers_ (\tt -> tt { tt2MSL = val })
-- | 75 second delay.
tcpKeepIntVal :: SlowTicks
tcpKeepIntVal = 150
-- | The timer that handles the TIME_WAIT, as well as the idle timeout.
handle2MSL :: Sock ()
handle2MSL =
do whenTimer tt2MSL $
do TcpSocket { .. } <- getTcpSocket
let TcpTimers { .. } = tcpTimers
if tcpState /= TimeWait && ttIdle <= ttMaxIdle
then set2MSL tcpKeepIntVal
else closeSocket
-- RTO -------------------------------------------------------------------------
-- | Update segments in the outgoing window, decrementing their RTO timers, and
-- retransmitting them if it expires.
handleRTO :: Sock ()
handleRTO = do
s <- getState
when (s /= Closed) (F.mapM_ outputSegment =<< modifyTcpSocket update)
where
update tcp = (segs,tcp { tcpOut = win' })
where
(segs,win') = genRetransmitSegments (tcpOut tcp)
-- | Calibrate the RTO timer, as specified by RFC-6298.
calibrateRTO :: POSIXTime -> POSIXTime -> TcpTimers -> TcpTimers
calibrateRTO sent ackd tt
| ttSRTT tt == 0 = initial
| otherwise = rolling
where
-- round trip measurement
r = ackd - sent
-- no data has been sent before, seed the RTO values.
initial = updateRTO tt
{ ttSRTT = r
, ttRTTVar = r / 2
}
-- data has been sent, update based on previous values
alpha = 0.125
beta = 0.25
rttvar = (1 - beta) * ttRTTVar tt + beta * abs (ttSRTT tt * r)
srtt = (1 - alpha) * ttSRTT tt + alpha * r
rolling = updateRTO tt
{ ttRTTVar = rttvar
, ttSRTT = srtt
}
-- update the RTO timer length, bounding it at 64 seconds (128 ticks)
updateRTO tt' = tt'
{ ttRTO = min 128 (ceiling (ttSRTT tt' + max 0.5 (2 * ttRTTVar tt')))
}