This repository has been archived by the owner on Jul 9, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTunnel.hs
127 lines (113 loc) · 4.32 KB
/
Tunnel.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
module IRC.Tunnel
(
Tunnel(..)
, newTunnel
, readTunnel
, tryReadTunnel
, writeTunnel
, tryWriteTunnel
, echoThread
)
where
import Control.Concurrent.STM.TBMChan
import Control.Concurrent.STM
import Control.Concurrent
import Control.Proxy as P hiding (hGetLineS, hPutStrLnD)
import Control.Proxy.Trans.Either
import qualified Control.Exception as E
import Control.Monad
import Data.IORef
import System.Time (getClockTime, diffClockTimes, TimeDiff(..))
import IRC.Internal as II
import IRC.Message (TMessage, mkPing)
import IRC.Error
-- | A tunnel is a connection to the server, it receives and sends message through
-- reader and writer channels.
data Tunnel = Tunnel {
-- | Bounded connection between the tunnel and the server
tServer :: II.Server
-- | Read end for downstream, write end for upstream
, tReader :: WriteChan
-- | Write end for downstream, write end for upstream
, tWriter :: ReadChan
-- | ThreadId of the parser which handles incoming messages
, tParserThread :: ThreadId
-- | ThreadId of the sender which handles outcoming messages
, tSenderThread :: ThreadId
}
instance Eq Tunnel where
t1 == t2 = tServer t1 == tServer t2
instance Show Tunnel where
show t = show $ tServer t
-- | Create a singleton to a server.
-- 'newTunnel' server bufsize creates a tunnel with buffer size of bufsize.
newTunnel :: II.Server -> Int -> IO Tunnel
newTunnel server bufsize = do
rch <- newTBMChanIO bufsize
wch <- newTBMChanIO bufsize
rtid <- forkIO $ do
r <- runProxy $ runEitherK $ hGetLineSE (sHandle server) >-> toMessage >-> timeStamp >-> writeTBMChanS rch
case r of
Left e -> E.throw e -- Rethrow exceptions in IO monad
Right _ -> return ()
wtid <- forkIO $ do
r <- runProxy $ runEitherK $ readTBMChanS wch >-> unTimeStamp >-> toRaw >-> hPutStrLnDE (sHandle server)
case r of
Left e -> E.throw e -- Rethrow exceptions in IO monad
Right _ -> return ()
return $ Tunnel server rch wch rtid wtid
-- | Timer thread
{-timer :: Server -> ReadChan -> WriteChan -> IO ()-}
{-timer h rch wch = forever $ do-}
{-m <- tryPeekTBMChan rch-}
{-case m of-}
{-Just Nothing -> return ()-}
{-Just m' -> if (tMsgCmd == Command PONG)-}
{-then getClockTime >>= writeIORef (sLastPong h)-}
{-else -}
-- | Read data from a tunnel, if tunnel is empty, it will block and wait for input.
readTunnel :: Tunnel -> IO TMessage
readTunnel t = do
m <- atomically $ readTBMChan $ tReader t
case m of
Nothing -> E.throw TunnelIsClosed
Just m' -> return m'
-- | Read data from a tunnel, since it uses 'TBMChan', notice that this function won't block,
-- if the channel is empty, it will return Nothing
tryReadTunnel :: Tunnel -> IO (Maybe TMessage)
tryReadTunnel t = do
msg <- atomically $ tryReadTBMChan $ tReader t
case msg of
Nothing -> E.throw TunnelIsClosed
Just Nothing -> return Nothing
Just (Just m) -> return $ Just m
-- | Write data to a tunnel, since it uses 'TBMChan', the channel may block if it's full.
writeTunnel :: Tunnel -> TMessage -> IO ()
writeTunnel t m = atomically $ writeTBMChan (tWriter t) m
-- | Write data to a tunnel without retry, return @IO True@ if data is successfully written,
-- otherwise, return @IO False@
tryWriteTunnel :: Tunnel -> TMessage -> IO Bool
tryWriteTunnel t m = do
r <- atomically $ tryWriteTBMChan (tWriter t) m
case r of
Nothing -> E.throw TunnelIsClosed
Just rr -> return rr
-- | Echo thread checks every tunnel in the list, if the sLastPong time exceeded timeout time,
-- it will close that channel and throw a ServerNoResponse exception.
echoThread :: IORef [Tunnel] -> IO ()
echoThread ts = forever $ do
ts' <- readIORef ts
time <- getClockTime
mapM_ (checkServer time) ts'
threadDelay 1000000
putStrLn "Tick..."
where checkServer t tn = do
lastPing <- readIORef refLastPing
lastPong <- readIORef refLastPong
let diffSec = tdSec $ diffClockTimes lastPong lastPing
in when (diffSec > 6) (throwE ServerNoResponse)
let pingDiff = tdSec $ diffClockTimes t lastPing
h = sHost . tServer $ tn
in when (pingDiff >= 3) (putStrLn ("Ping " ++ h) >> writeTunnel tn (mkPing h) >> writeIORef refLastPing t)
where refLastPing = sLastPing . tServer $ tn
refLastPong = sLastPong . tServer $ tn