packages feed

kademlia-1.0.0.0: src/Network/Kademlia/ReplyQueue.hs

{-|
Module      : Network.Kademlia.ReplyQueue
Description : A queue allowing to register handlers for expected replies

Network.Kademlia.ReplyQueue implements a Queue designed for registering
handlers for expected replies.

The handlers are represented by unbound channels from Control.Concurrency.Chan.
-}

module Network.Kademlia.ReplyQueue
    ( ReplyType(..)
    , ReplyRegistration(..)
    , Reply(..)
    , ReplyQueue
    , emptyReplyQueue
    , register
    , dispatch
    , flush
    ) where

import Control.Concurrent
import Control.Concurrent.STM
import Control.Concurrent.Chan
import Control.Monad (liftM, forM_)
import Control.Monad.Trans.Maybe
import Data.List (find, delete)

import Network.Kademlia.Types

-- | The different types a replied signal could possibly have.
--
--   Note that these are only those Command types, which are replies to some
--   sort of request. Therefore, most Command types aren't contained in this
--   type.
data ReplyType i = R_PONG
                 | R_RETURN_VALUE i
                 | R_RETURN_NODES i
                   deriving (Eq)

-- | The representation of registered replies
data ReplyRegistration i = RR {
      replyTypes  :: [ReplyType i]
    , replyOrigin :: i
    } deriving (Eq)

-- | Convert a Signal into its ReplyRegistration representation
toRegistration :: Signal i a -> Maybe (ReplyRegistration i)
toRegistration sig = case rType . command $ sig of
                        Nothing -> Nothing
                        Just rt -> Just (RR [rt] origin)
    where origin = nodeId . source $ sig

          rType :: Command i a -> Maybe (ReplyType i)
          rType  PONG               = Just  R_PONG
          rType (RETURN_VALUE id _) = Just (R_RETURN_VALUE id)
          rType (RETURN_NODES id _) = Just (R_RETURN_NODES id)
          rType _ = Nothing

-- | Compare wether two ReplyRegistrations match
matchRegistrations :: (Eq i) => ReplyRegistration i -> ReplyRegistration i -> Bool
matchRegistrations (RR rtsA idA) (RR rtsB idB) =
    idA == idB && (all (`elem` rtsA) rtsB || all (`elem` rtsB) rtsA)

-- | The actual type of a replay
data Reply i a = Answer (Signal i a)
               | Timeout (ReplyRegistration i)
               | Closed
                 deriving (Eq)

-- | The actual type representing a ReplyQueue
newtype ReplyQueue i a = RQ (TVar [(ReplyRegistration i, Chan (Reply i a), ThreadId)])

-- | Create an empty ReplyQueue
emptyReplyQueue :: IO (ReplyQueue i a)
emptyReplyQueue = atomically . liftM RQ $ newTVar []

-- | Register a channel as handler for a reply
register :: (Eq i) => ReplyRegistration i -> ReplyQueue i a -> Chan (Reply i a)
         -> IO ()
register reg (RQ rq) chan = do
    tId <- timeoutThread chan reg (RQ rq)
    atomically $ do
        queue <- readTVar $ rq
        writeTVar rq $ queue ++ [(reg, chan, tId)]

timeoutThread :: (Eq i) => Chan (Reply i a) -> ReplyRegistration i
              -> ReplyQueue i a -> IO ThreadId
timeoutThread chan reg (RQ rq) = forkIO $ do
    -- Wait 5 seconds
    threadDelay 5000000

    -- Remove the ReplyRegistration from the ReplyQueue
    myTId <- myThreadId
    atomically $ do
        queue <- readTVar $ rq
        case find (\(_, _, tId) -> tId == myTId) queue of
            Just rqElem -> writeTVar rq $ delete rqElem queue
            _ -> return ()

    -- Send Timeout signal
    writeChan chan . Timeout $ reg

-- | Try to send a received Signal over the registered handler channel and
--   return wether it succeeded
dispatch :: (Eq i) => Signal i a -> ReplyQueue i a -> IO Bool
dispatch sig (RQ rq) = do
    result <- atomically $ do
        queue <- readTVar $ rq
        case toRegistration sig of
            Just regA -> case find (matches regA) queue of
                Just reg -> do
                    -- Remove registration from queue
                    writeTVar rq $ delete reg queue
                    return . Just $ reg

                Nothing -> return Nothing
            Nothing  -> return Nothing

    case result of
        Just (_, chan, tId) -> do
            -- Kill the timeout thread
            killThread tId

            -- Send the signal
            writeChan chan $ Answer sig
            return True
        _ -> return False

    where matches regA (regB, _, _) = matchRegistrations regA regB

-- | Send Closed signal to all handlers and empty ReplyQueue
flush :: ReplyQueue i a -> IO ()
flush (RQ rq) = do
    queue <- atomically $ do
        queue <- readTVar $ rq
        writeTVar rq $ []
        return queue

    forM_ queue $ \(_, chan, tId) -> do
        killThread tId
        writeChan chan Closed