{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
module Haxl.RedisCache
( cached
, cached'
, remove
, removeAll
, initRedisState
) where
import Control.Concurrent.Async
import Control.Concurrent.QSem
import qualified Control.Exception (SomeException, bracket_, try)
import Control.Monad (void)
import Data.Aeson (FromJSON, ToJSON, decodeStrict,
encode)
import Data.ByteString (ByteString)
import qualified Data.ByteString as B (concat)
import Data.ByteString.Lazy (toStrict)
import Data.Hashable (Hashable (..))
import Data.Typeable (Typeable)
import Database.Redis (Connection, del, get, runRedis, set)
import Haxl.Core hiding (fetchReq)
newtype Conn = Conn Connection
instance Eq Conn where
_ == _ = True
instance Show Conn where
show _ = "Conn"
genKey :: ByteString -> ByteString -> ByteString
genKey pref k = B.concat [pref, ":", k]
getData_ :: Connection -> ByteString -> IO (Maybe ByteString)
getData_ conn k = runRedis conn $ either (const Nothing) id <$> get k
setData_ :: Connection -> ByteString -> ByteString -> IO ()
setData_ conn k = runRedis conn . void . set k
delData_ :: Connection -> [ByteString] -> IO ()
delData_ conn ks = runRedis conn . void $ del ks
data RedisReq a where
GetData :: Conn -> ByteString -> RedisReq (Maybe ByteString)
SetData :: Conn -> ByteString -> ByteString -> RedisReq ()
DelData :: Conn -> [ByteString] -> RedisReq ()
deriving (Typeable)
deriving instance Eq (RedisReq a)
instance Hashable (RedisReq a) where
hashWithSalt s (GetData _ k) = hashWithSalt s (1::Int, k)
hashWithSalt s (SetData _ k v) = hashWithSalt s (2::Int, k, v)
hashWithSalt s (DelData _ ks) = hashWithSalt s (3::Int, ks)
deriving instance Show (RedisReq a)
instance ShowP RedisReq where showp = show
instance StateKey RedisReq where
data State RedisReq = RedisState { numThreads :: Int, prefix :: ByteString }
instance DataSourceName RedisReq where
dataSourceName _ = "RedisDataSource"
instance DataSource u RedisReq where
fetch = doFetch
doFetch
:: State RedisReq
-> Flags
-> u
-> PerformFetch RedisReq
doFetch _state _flags _ = AsyncFetch $ \reqs inner -> do
sem <- newQSem $ numThreads _state
asyncs <- mapM (fetchAsync sem (prefix _state)) reqs
inner
mapM_ wait asyncs
fetchAsync :: QSem -> ByteString -> BlockedFetch RedisReq -> IO (Async ())
fetchAsync sem pref req = async $
Control.Exception.bracket_ (waitQSem sem) (signalQSem sem) $ fetchSync pref req
fetchSync :: ByteString -> BlockedFetch RedisReq -> IO ()
fetchSync pref (BlockedFetch req rvar) = do
e <- Control.Exception.try $ fetchReq pref req
case e of
Left ex -> putFailure rvar (ex :: Control.Exception.SomeException)
Right a -> putSuccess rvar a
fetchReq :: ByteString -> RedisReq a -> IO a
fetchReq pref (GetData (Conn conn) k) = getData_ conn $ genKey pref k
fetchReq pref (SetData (Conn conn) k v) = setData_ conn (genKey pref k) v
fetchReq pref (DelData (Conn conn) ks) = delData_ conn $ map (genKey pref) ks
initRedisState :: Int -> ByteString -> State RedisReq
initRedisState = RedisState
getData :: FromJSON v => Connection -> ByteString -> GenHaxl u w (Maybe v)
getData conn k = maybe Nothing decodeStrict <$> dataFetch (GetData (Conn conn) k)
setData :: ToJSON v => Connection -> ByteString -> v -> GenHaxl u w ()
setData conn k v = uncachedRequest . SetData (Conn conn) k . toStrict $ encode v
delData :: Connection -> [ByteString] -> GenHaxl u w ()
delData conn = uncachedRequest . DelData (Conn conn)
cached :: (FromJSON v, ToJSON v) => (u -> Maybe Connection) -> ByteString -> GenHaxl u w (Maybe v) -> GenHaxl u w (Maybe v)
cached redis k io = do
h <- redis <$> env userEnv
go h k io
where go :: (FromJSON v, ToJSON v) => Maybe Connection -> ByteString -> GenHaxl u w (Maybe v) -> GenHaxl u w (Maybe v)
go Nothing _ io0 = io0
go (Just conn) k0 io0 = do
res <- getData conn k0
case res of
Just v -> return (Just v)
Nothing -> do
v <- io0
case v of
Nothing -> return Nothing
Just v0 -> do
setData conn k0 v0
return v
cached' :: (FromJSON v, ToJSON v) => (u -> Maybe Connection) -> ByteString -> GenHaxl u w v -> GenHaxl u w v
cached' redis k io = do
h <- redis <$> env userEnv
go h k io
where go :: (FromJSON v, ToJSON v) => Maybe Connection -> ByteString -> GenHaxl u w v -> GenHaxl u w v
go Nothing _ io0 = io0
go (Just conn) k0 io0 = do
res <- getData conn k0
case res of
Just v -> return v
Nothing -> do
v <- io0
setData conn k0 v
return v
remove :: (u -> Maybe Connection) -> ByteString -> GenHaxl u w ()
remove redis k = removeAll redis [k]
removeAll :: (u -> Maybe Connection) -> [ByteString] -> GenHaxl u w ()
removeAll redis k = do
h <- redis <$> env userEnv
case h of
Nothing -> return ()
Just conn -> delData conn k