memcache-0.3.0.0: Database/Memcache/Server.hs
{-# LANGUAGE RecordWildCards #-}
{-|
Module : Database.Memcache.Server
Description : Server Handling
Copyright : (c) David Terei, 2016
License : BSD
Maintainer : code@davidterei.com
Stability : stable
Portability : GHC
Handles the connections between a Memcached client and a single server.
Memcached expected errors (part of protocol) are returned in the Response,
unexpected errors (e.g., network failure) are thrown as exceptions. While
the Server datatype supports a `failed` and `failedAt` flag for managing
retries, it's up to consumers to use this.
-}
module Database.Memcache.Server (
-- * Server
Server(sid, failed), newServer, sendRecv, withSocket, close
) where
import Database.Memcache.SASL
import Database.Memcache.Socket
import Control.Exception
import Data.Hashable
import Data.IORef
import Data.Pool
import Data.Time.Clock (NominalDiffTime)
import Data.Time.Clock.POSIX (POSIXTime)
import Network.Socket (getAddrInfo, HostName, ServiceName)
import qualified Network.Socket as S
-- Connection pool constants.
-- TODO: make configurable
sSTRIPES, sCONNECTIONS :: Int
sKEEPALIVE :: NominalDiffTime
sSTRIPES = 1
sCONNECTIONS = 1
sKEEPALIVE = 300
-- | Memcached server connection.
data Server = Server {
-- | ID of server for consistent hashing.
sid :: {-# UNPACK #-} !Int,
-- | Connection pool to server.
pool :: Pool Socket,
-- | Hostname of server.
addr :: !HostName,
-- | Port number of server.
port :: !ServiceName,
-- | Credentials for server.
auth :: !Authentication,
-- | When did the server fail? 0 if it is alive.
failed :: IORef POSIXTime
-- TODO:
-- weight :: Double
-- tansport :: Transport (UDP vs. TCP)
-- poolLim :: Int (pooled connection limit)
-- cnxnBuf :: IORef ByteString
}
instance Show Server where
show Server{..} =
"Server [" ++ show sid ++ "] " ++ addr ++ ":" ++ show port
instance Eq Server where
(==) x y = sid x == sid y
instance Ord Server where
compare x y = compare (sid x) (sid y)
-- | Create a new Memcached server connection.
newServer :: HostName -> ServiceName -> Authentication -> IO Server
newServer host port auth = do
fat <- newIORef 0
pSock <- createPool connectSocket releaseSocket
sSTRIPES sKEEPALIVE sCONNECTIONS
return Server
{ sid = serverHash
, pool = pSock
, addr = host
, port = port
, auth = auth
, failed = fat
}
where
serverHash = hash (host, port)
connectSocket = do
let hints = S.defaultHints {
S.addrSocketType = S.Stream
}
addr:_ <- getAddrInfo (Just hints) (Just host) (Just port)
bracketOnError
(S.socket (S.addrFamily addr) (S.addrSocketType addr) (S.addrProtocol addr))
releaseSocket
(\s -> do
S.connect s $ S.addrAddress addr
S.setSocketOption s S.KeepAlive 1
S.setSocketOption s S.NoDelay 1
authenticate s auth
return s
)
releaseSocket = S.close
-- | Send and receive a single request/response pair to the Memcached server.
sendRecv :: Server -> Request -> IO Response
{-# INLINE sendRecv #-}
sendRecv svr msg = withSocket svr $ \s -> do
send s msg
recv s
-- | Run a function with access to an server socket for using 'send' and
-- 'recv'.
withSocket :: Server -> (Socket -> IO a) -> IO a
{-# INLINE withSocket #-}
withSocket svr = withResource $ pool svr
-- | Close the server connection. If you perform another operation after this,
-- the connection will be re-established.
close :: Server -> IO ()
{-# INLINE close #-}
close srv = destroyAllResources $ pool srv