{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE GADTs #-}
module Keter.RateLimiter.Cache
(
Algorithm(..)
, Cache(..)
, CacheStore(..)
, InMemoryStore(..)
, ResettableStore(..)
, CreateStore(..)
, readCache
, writeCache
, deleteCache
, incrementCache
, newCache
, createInMemoryStore
, clearInMemoryStore
, cacheReset
, algorithmPrefix
, makeCacheKey
, secondsToTimeSpec
, startAutoPurge
, startCustomPurgeTokenBucket
, startCustomPurgeLeakyBucket
, startTokenBucketWorker
, startLeakyBucketWorker
, createTokenBucketEntry
, createLeakyBucketEntry
) where
import Control.Concurrent.STM
import Control.Concurrent.MVar (putMVar, takeMVar, newMVar, readMVar, MVar)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad (void, forever)
import Control.Concurrent (forkIO, threadDelay)
import Data.Aeson (ToJSON, FromJSON, decodeStrict, encode)
import qualified Data.ByteString.Lazy as LBS
import Data.Text (Text)
import qualified Data.TinyLRU as TinyLRU
import Data.Text.Encoding (encodeUtf8, decodeUtf8')
import qualified Data.Cache as C
import System.Clock (TimeSpec(..), Clock(Monotonic), getTime, toNanoSecs)
import Data.Time.Clock.POSIX (getPOSIXTime)
import qualified StmContainers.Map as StmMap
import qualified Control.Concurrent.STM.TQueue as TQueue
import qualified Focus
import Keter.RateLimiter.Types (TokenBucketState(..), LeakyBucketState(..))
import Keter.RateLimiter.AutoPurge
import Keter.RateLimiter.TokenBucketWorker (startTokenBucketWorker)
import Data.Maybe (fromMaybe)
data Algorithm = FixedWindow | SlidingWindow | TokenBucket | LeakyBucket | TinyLRU
deriving (Int -> Algorithm -> ShowS
[Algorithm] -> ShowS
Algorithm -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Algorithm] -> ShowS
$cshowList :: [Algorithm] -> ShowS
show :: Algorithm -> String
$cshow :: Algorithm -> String
showsPrec :: Int -> Algorithm -> ShowS
$cshowsPrec :: Int -> Algorithm -> ShowS
Show, Algorithm -> Algorithm -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Algorithm -> Algorithm -> Bool
$c/= :: Algorithm -> Algorithm -> Bool
== :: Algorithm -> Algorithm -> Bool
$c== :: Algorithm -> Algorithm -> Bool
Eq)
algorithmPrefix :: Algorithm
-> Text
algorithmPrefix :: Algorithm -> Text
algorithmPrefix Algorithm
FixedWindow = Text
"rate_limiter"
algorithmPrefix Algorithm
SlidingWindow = Text
"timestamps"
algorithmPrefix Algorithm
TokenBucket = Text
"token_bucket"
algorithmPrefix Algorithm
LeakyBucket = Text
"leaky_bucket"
algorithmPrefix Algorithm
TinyLRU = Text
"tiny_lru"
data Cache store = Cache
{ forall store. Cache store -> Algorithm
cacheAlgorithm :: Algorithm
, forall store. Cache store -> store
cacheStore :: store
}
class MonadIO m => CacheStore store v m | store -> v where
readStore :: store
-> Text
-> Text
-> m (Maybe v)
writeStore :: store
-> Text
-> Text
-> v
-> Int
-> m ()
deleteStore :: store
-> Text
-> Text
-> m ()
incStore :: (FromJSON v, ToJSON v, Ord v, Num v)
=> store
-> Text
-> Text
-> Int
-> m v
incStore store
store Text
prefix Text
key Int
expiresIn = do
Maybe v
mval <- forall store v (m :: * -> *).
CacheStore store v m =>
store -> Text -> Text -> m (Maybe v)
readStore store
store Text
prefix Text
key
let newVal :: v
newVal = case Maybe v
mval of
Maybe v
Nothing -> v
1
Just v
v -> if v
v forall a. Ord a => a -> a -> Bool
<= v
0 then v
1 else v
v forall a. Num a => a -> a -> a
+ v
1
forall store v (m :: * -> *).
CacheStore store v m =>
store -> Text -> Text -> v -> Int -> m ()
writeStore store
store Text
prefix Text
key v
newVal Int
expiresIn
forall (m :: * -> *) a. Monad m => a -> m a
return v
newVal
class ResettableStore store where
resetStore :: store -> IO ()
data InMemoryStore (a :: Algorithm) where
CounterStore :: TVar (C.Cache Text Text) -> InMemoryStore 'FixedWindow
TimestampStore :: TVar (StmMap.Map Text [Double]) -> InMemoryStore 'SlidingWindow
TokenBucketStore :: TVar (StmMap.Map Text TokenBucketEntry) -> InMemoryStore 'TokenBucket
LeakyBucketStore :: TVar (StmMap.Map Text LeakyBucketEntry) -> InMemoryStore 'LeakyBucket
TinyLRUStore :: TVar (TinyLRU.TinyLRUCache s) -> InMemoryStore 'TinyLRU
class CreateStore (a :: Algorithm) where
createStore :: IO (InMemoryStore a)
secondsToTimeSpec :: Int
-> IO TimeSpec
secondsToTimeSpec :: Int -> IO TimeSpec
secondsToTimeSpec Int
seconds = do
TimeSpec
now <- Clock -> IO TimeSpec
getTime Clock
Monotonic
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ TimeSpec
now forall a. Num a => a -> a -> a
+ Int64 -> Int64 -> TimeSpec
TimeSpec (forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
seconds) Int64
0
instance CreateStore 'FixedWindow where
createStore :: IO (InMemoryStore 'FixedWindow)
createStore = forall (a :: Algorithm).
(TVar (Cache Text Text) -> InMemoryStore a) -> IO (InMemoryStore a)
createStoreWith TVar (Cache Text Text) -> InMemoryStore 'FixedWindow
CounterStore
instance CreateStore 'SlidingWindow where
createStore :: IO (InMemoryStore 'SlidingWindow)
createStore = do
Map Text [Double]
emptyMap <- forall a. STM a -> IO a
atomically (forall key value. STM (Map key value)
StmMap.new :: STM (StmMap.Map Text [Double]))
TVar (Map Text [Double])
tvar <- forall a. a -> IO (TVar a)
newTVarIO Map Text [Double]
emptyMap
forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ TVar (Map Text [Double]) -> InMemoryStore 'SlidingWindow
TimestampStore TVar (Map Text [Double])
tvar
instance CreateStore 'TokenBucket where
createStore :: IO (InMemoryStore 'TokenBucket)
createStore = do
Map Text TokenBucketEntry
emptyMap <- forall a. STM a -> IO a
atomically (forall key value. STM (Map key value)
StmMap.new :: STM (StmMap.Map Text TokenBucketEntry))
TVar (Map Text TokenBucketEntry)
tvar <- forall a. a -> IO (TVar a)
newTVarIO Map Text TokenBucketEntry
emptyMap
forall (f :: * -> *) a. Functor f => f a -> f ()
void forall a b. (a -> b) -> a -> b
$ Map Text TokenBucketEntry -> Integer -> Integer -> IO ThreadId
startCustomPurgeTokenBucket Map Text TokenBucketEntry
emptyMap (Integer
60 :: Integer) (Integer
3600 :: Integer)
forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ TVar (Map Text TokenBucketEntry) -> InMemoryStore 'TokenBucket
TokenBucketStore TVar (Map Text TokenBucketEntry)
tvar
instance CreateStore 'LeakyBucket where
createStore :: IO (InMemoryStore 'LeakyBucket)
createStore = do
Map Text LeakyBucketEntry
emptyMap <- forall a. STM a -> IO a
atomically (forall key value. STM (Map key value)
StmMap.new :: STM (StmMap.Map Text LeakyBucketEntry))
TVar (Map Text LeakyBucketEntry)
tvar <- forall a. a -> IO (TVar a)
newTVarIO Map Text LeakyBucketEntry
emptyMap
forall (f :: * -> *) a. Functor f => f a -> f ()
void forall a b. (a -> b) -> a -> b
$ Map Text LeakyBucketEntry -> Integer -> Integer -> IO ThreadId
startCustomPurgeLeakyBucket Map Text LeakyBucketEntry
emptyMap (Integer
60 :: Integer) (Integer
3600 :: Integer)
forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ TVar (Map Text LeakyBucketEntry) -> InMemoryStore 'LeakyBucket
LeakyBucketStore TVar (Map Text LeakyBucketEntry)
tvar
instance CreateStore 'TinyLRU where
createStore :: IO (InMemoryStore 'TinyLRU)
createStore = forall s. TVar (TinyLRUCache s) -> InMemoryStore 'TinyLRU
TinyLRUStore forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ forall a. a -> STM (TVar a)
newTVar forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< forall s. Int -> STM (TinyLRUCache s)
TinyLRU.initTinyLRU Int
100)
createStoreWith :: (TVar (C.Cache Text Text) -> InMemoryStore a) -> IO (InMemoryStore a)
createStoreWith :: forall (a :: Algorithm).
(TVar (Cache Text Text) -> InMemoryStore a) -> IO (InMemoryStore a)
createStoreWith TVar (Cache Text Text) -> InMemoryStore a
mkStore = do
Cache Text Text
rawCache <- forall k v. Maybe TimeSpec -> IO (Cache k v)
C.newCache forall a. Maybe a
Nothing
MVar Integer
purgeInterval <- forall a. a -> IO (MVar a)
newMVar (Integer
60 :: Integer)
MVar ()
purgeSignal <- forall a. a -> IO (MVar a)
newMVar ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void forall a b. (a -> b) -> a -> b
$ IO () -> IO ThreadId
forkIO forall a b. (a -> b) -> a -> b
$ forall (f :: * -> *) a b. Applicative f => f a -> f b
forever forall a b. (a -> b) -> a -> b
$ do
forall a. MVar a -> IO a
takeMVar MVar ()
purgeSignal
Integer
interval <- forall a. MVar a -> IO a
readMVar MVar Integer
purgeInterval
TimeSpec
startTime <- Clock -> IO TimeSpec
getTime Clock
Monotonic
forall k v. (Eq k, Hashable k) => Cache k v -> IO ()
C.purgeExpired Cache Text Text
rawCache
TimeSpec
endTime <- Clock -> IO TimeSpec
getTime Clock
Monotonic
let elapsedMicros :: Integer
elapsedMicros = (TimeSpec -> Integer
toNanoSecs TimeSpec
endTime forall a. Num a => a -> a -> a
- TimeSpec -> Integer
toNanoSecs TimeSpec
startTime) forall a. Integral a => a -> a -> a
`div` Integer
1000
remainingMicros :: Integer
remainingMicros = forall a. Ord a => a -> a -> a
max (Integer
0 :: Integer) (Integer
interval forall a. Num a => a -> a -> a
* Integer
1000000 forall a. Num a => a -> a -> a
- Integer
elapsedMicros)
TimeSpec -> Integer -> MVar () -> IO ()
waitUntilNextPurge TimeSpec
startTime Integer
remainingMicros MVar ()
purgeSignal
TVar (Cache Text Text)
tvar <- forall a. a -> IO (TVar a)
newTVarIO Cache Text Text
rawCache
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ TVar (Cache Text Text) -> InMemoryStore a
mkStore TVar (Cache Text Text)
tvar
waitUntilNextPurge :: TimeSpec -> Integer -> MVar () -> IO ()
waitUntilNextPurge :: TimeSpec -> Integer -> MVar () -> IO ()
waitUntilNextPurge TimeSpec
startTime Integer
remainingMicros MVar ()
purgeSignal = do
TimeSpec
currentTime <- Clock -> IO TimeSpec
getTime Clock
Monotonic
let elapsedMicros :: Integer
elapsedMicros = (TimeSpec -> Integer
toNanoSecs TimeSpec
currentTime forall a. Num a => a -> a -> a
- TimeSpec -> Integer
toNanoSecs TimeSpec
startTime) forall a. Integral a => a -> a -> a
`div` Integer
1000
if Integer
elapsedMicros forall a. Ord a => a -> a -> Bool
>= Integer
remainingMicros
then forall a. MVar a -> a -> IO ()
putMVar MVar ()
purgeSignal ()
else do
let sleepMicros :: Int
sleepMicros = forall a b. (Integral a, Num b) => a -> b
fromIntegral (forall a. Ord a => a -> a -> a
min Integer
remainingMicros (forall a. Integral a => a -> Integer
toInteger (forall a. Bounded a => a
maxBound :: Int))) :: Int
Int -> IO ()
threadDelay Int
sleepMicros
forall a. MVar a -> a -> IO ()
putMVar MVar ()
purgeSignal ()
createInMemoryStore :: forall (a :: Algorithm). CreateStore a => IO (InMemoryStore a)
createInMemoryStore :: forall (a :: Algorithm). CreateStore a => IO (InMemoryStore a)
createInMemoryStore = forall (a :: Algorithm). CreateStore a => IO (InMemoryStore a)
createStore @a
newCache :: Algorithm
-> store
-> Cache store
newCache :: forall store. Algorithm -> store -> Cache store
newCache Algorithm
algo store
store = Cache
{ cacheAlgorithm :: Algorithm
cacheAlgorithm = Algorithm
algo
, cacheStore :: store
cacheStore = store
store
}
readCache :: (CacheStore store v IO)
=> Cache store
-> Text
-> IO (Maybe v)
readCache :: forall store v.
CacheStore store v IO =>
Cache store -> Text -> IO (Maybe v)
readCache Cache store
cache Text
unprefixedKey =
forall store v (m :: * -> *).
CacheStore store v m =>
store -> Text -> Text -> m (Maybe v)
readStore (forall store. Cache store -> store
cacheStore Cache store
cache) (Algorithm -> Text
algorithmPrefix forall a b. (a -> b) -> a -> b
$ forall store. Cache store -> Algorithm
cacheAlgorithm Cache store
cache)
(Algorithm -> Text
algorithmPrefix (forall store. Cache store -> Algorithm
cacheAlgorithm Cache store
cache) forall a. Semigroup a => a -> a -> a
<> Text
":" forall a. Semigroup a => a -> a -> a
<> Text
unprefixedKey)
writeCache :: (CacheStore store v IO)
=> Cache store
-> Text
-> v
-> Int
-> IO ()
writeCache :: forall store v.
CacheStore store v IO =>
Cache store -> Text -> v -> Int -> IO ()
writeCache Cache store
cache Text
unprefixedKey v
val Int
expiresIn =
forall store v (m :: * -> *).
CacheStore store v m =>
store -> Text -> Text -> v -> Int -> m ()
writeStore (forall store. Cache store -> store
cacheStore Cache store
cache) (Algorithm -> Text
algorithmPrefix forall a b. (a -> b) -> a -> b
$ forall store. Cache store -> Algorithm
cacheAlgorithm Cache store
cache)
(Algorithm -> Text
algorithmPrefix (forall store. Cache store -> Algorithm
cacheAlgorithm Cache store
cache) forall a. Semigroup a => a -> a -> a
<> Text
":" forall a. Semigroup a => a -> a -> a
<> Text
unprefixedKey) v
val Int
expiresIn
deleteCache :: (CacheStore store v IO)
=> Cache store
-> Text
-> IO ()
deleteCache :: forall store v.
CacheStore store v IO =>
Cache store -> Text -> IO ()
deleteCache Cache store
cache Text
unprefixedKey =
forall store v (m :: * -> *).
CacheStore store v m =>
store -> Text -> Text -> m ()
deleteStore (forall store. Cache store -> store
cacheStore Cache store
cache) (Algorithm -> Text
algorithmPrefix forall a b. (a -> b) -> a -> b
$ forall store. Cache store -> Algorithm
cacheAlgorithm Cache store
cache)
(Algorithm -> Text
algorithmPrefix (forall store. Cache store -> Algorithm
cacheAlgorithm Cache store
cache) forall a. Semigroup a => a -> a -> a
<> Text
":" forall a. Semigroup a => a -> a -> a
<> Text
unprefixedKey)
incrementCache :: (CacheStore store v IO, FromJSON v, ToJSON v, Ord v, Num v)
=> Cache store
-> Text
-> Int
-> IO v
incrementCache :: forall store v.
(CacheStore store v IO, FromJSON v, ToJSON v, Ord v, Num v) =>
Cache store -> Text -> Int -> IO v
incrementCache Cache store
cache Text
unprefixedKey Int
expiresIn = do
let fullKey :: Text
fullKey = Algorithm -> Text
algorithmPrefix (forall store. Cache store -> Algorithm
cacheAlgorithm Cache store
cache) forall a. Semigroup a => a -> a -> a
<> Text
":" forall a. Semigroup a => a -> a -> a
<> Text
unprefixedKey
prefix :: Text
prefix = Algorithm -> Text
algorithmPrefix (forall store. Cache store -> Algorithm
cacheAlgorithm Cache store
cache)
forall store v (m :: * -> *).
(CacheStore store v m, FromJSON v, ToJSON v, Ord v, Num v) =>
store -> Text -> Text -> Int -> m v
incStore (forall store. Cache store -> store
cacheStore Cache store
cache) Text
prefix Text
fullKey Int
expiresIn
clearInMemoryStore :: ResettableStore store
=> store
-> IO ()
clearInMemoryStore :: forall store. ResettableStore store => store -> IO ()
clearInMemoryStore = forall store. ResettableStore store => store -> IO ()
resetStore
cacheReset :: ResettableStore store
=> Cache store
-> IO ()
cacheReset :: forall store. ResettableStore store => Cache store -> IO ()
cacheReset (Cache Algorithm
_ store
store) = forall store. ResettableStore store => store -> IO ()
resetStore store
store
createTokenBucketEntry :: TokenBucketState
-> IO TokenBucketEntry
createTokenBucketEntry :: TokenBucketState -> IO TokenBucketEntry
createTokenBucketEntry TokenBucketState
state = do
TVar TokenBucketState
stateVar <- forall a. a -> IO (TVar a)
newTVarIO TokenBucketState
state
TQueue (MVar Bool)
queue <- forall a. STM a -> IO a
atomically forall a. STM (TQueue a)
TQueue.newTQueue
TMVar ()
workerLock <- forall a. STM a -> IO a
atomically forall a. STM (TMVar a)
newEmptyTMVar
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ TVar TokenBucketState
-> TQueue (MVar Bool) -> TMVar () -> TokenBucketEntry
TokenBucketEntry TVar TokenBucketState
stateVar TQueue (MVar Bool)
queue TMVar ()
workerLock
createLeakyBucketEntry :: LeakyBucketState
-> IO LeakyBucketEntry
createLeakyBucketEntry :: LeakyBucketState -> IO LeakyBucketEntry
createLeakyBucketEntry LeakyBucketState
state = do
TVar LeakyBucketState
stateVar <- forall a. a -> IO (TVar a)
newTVarIO LeakyBucketState
state
TQueue (TMVar Bool)
queue <- forall a. STM a -> IO a
atomically forall a. STM (TQueue a)
TQueue.newTQueue
TMVar ()
workerLock <- forall a. STM a -> IO a
atomically forall a. STM (TMVar a)
newEmptyTMVar
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ TVar LeakyBucketState
-> TQueue (TMVar Bool) -> TMVar () -> LeakyBucketEntry
LeakyBucketEntry TVar LeakyBucketState
stateVar TQueue (TMVar Bool)
queue TMVar ()
workerLock
instance CacheStore (InMemoryStore 'FixedWindow) Int IO where
readStore :: InMemoryStore 'FixedWindow -> Text -> Text -> IO (Maybe Int)
readStore (CounterStore TVar (Cache Text Text)
tvar) Text
_prefix Text
key = do
Cache Text Text
cache <- forall a. TVar a -> IO a
readTVarIO TVar (Cache Text Text)
tvar
Maybe Text
mval <- forall k v. (Eq k, Hashable k) => Cache k v -> k -> IO (Maybe v)
C.lookup Cache Text Text
cache Text
key
case Maybe Text
mval of
Maybe Text
Nothing -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a. Maybe a
Nothing
Just Text
txt -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a. FromJSON a => ByteString -> Maybe a
decodeStrict (Text -> ByteString
encodeUtf8 Text
txt)
writeStore :: InMemoryStore 'FixedWindow -> Text -> Text -> Int -> Int -> IO ()
writeStore (CounterStore TVar (Cache Text Text)
tvar) Text
_prefix Text
key Int
val Int
expiresIn = do
let bs :: ByteString
bs = forall a. ToJSON a => a -> ByteString
encode Int
val
strictBs :: ByteString
strictBs = ByteString -> ByteString
LBS.toStrict ByteString
bs
jsonTxt :: Text
jsonTxt = case ByteString -> Either UnicodeException Text
decodeUtf8' ByteString
strictBs of
Left UnicodeException
_ -> Text
""
Right Text
txt -> Text
txt
TimeSpec
expiryTimeSpec <- Int -> IO TimeSpec
secondsToTimeSpec Int
expiresIn
forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
Cache Text Text
cache <- forall a. TVar a -> STM a
readTVar TVar (Cache Text Text)
tvar
forall k v.
(Eq k, Hashable k) =>
k -> v -> Cache k v -> Maybe TimeSpec -> STM ()
C.insertSTM Text
key Text
jsonTxt Cache Text Text
cache (forall a. a -> Maybe a
Just TimeSpec
expiryTimeSpec)
deleteStore :: InMemoryStore 'FixedWindow -> Text -> Text -> IO ()
deleteStore (CounterStore TVar (Cache Text Text)
tvar) Text
_prefix Text
key = do
Cache Text Text
cache <- forall a. TVar a -> IO a
readTVarIO TVar (Cache Text Text)
tvar
forall k v. (Eq k, Hashable k) => Cache k v -> k -> IO ()
C.delete Cache Text Text
cache Text
key
incStore :: (FromJSON Int, ToJSON Int, Ord Int, Num Int) =>
InMemoryStore 'FixedWindow -> Text -> Text -> Int -> IO Int
incStore (CounterStore TVar (Cache Text Text)
tvar) Text
_prefix Text
key Int
expiresIn = do
TimeSpec
now <- Clock -> IO TimeSpec
getTime Clock
Monotonic
TimeSpec
expiryTimeSpec <- Int -> IO TimeSpec
secondsToTimeSpec Int
expiresIn
forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
Cache Text Text
cache <- forall a. TVar a -> STM a
readTVar TVar (Cache Text Text)
tvar
Maybe Text
mval <- forall k v.
(Eq k, Hashable k) =>
Bool -> k -> Cache k v -> TimeSpec -> STM (Maybe v)
C.lookupSTM Bool
False Text
key Cache Text Text
cache TimeSpec
now
let currentVal :: Int
currentVal = case Maybe Text
mval of
Maybe Text
Nothing -> Int
0
Just Text
txt -> forall a. a -> Maybe a -> a
fromMaybe Int
0 (forall a. FromJSON a => ByteString -> Maybe a
decodeStrict (Text -> ByteString
encodeUtf8 Text
txt))
let newVal :: Int
newVal = Int
currentVal forall a. Num a => a -> a -> a
+ Int
1
let bs :: ByteString
bs = forall a. ToJSON a => a -> ByteString
encode Int
newVal
strictBs :: ByteString
strictBs = ByteString -> ByteString
LBS.toStrict ByteString
bs
jsonTxt :: Text
jsonTxt = case ByteString -> Either UnicodeException Text
decodeUtf8' ByteString
strictBs of
Left UnicodeException
_ -> Text
""
Right Text
txt -> Text
txt
forall k v.
(Eq k, Hashable k) =>
k -> v -> Cache k v -> Maybe TimeSpec -> STM ()
C.insertSTM Text
key Text
jsonTxt Cache Text Text
cache (forall a. a -> Maybe a
Just TimeSpec
expiryTimeSpec)
forall (m :: * -> *) a. Monad m => a -> m a
return Int
newVal
instance CacheStore (InMemoryStore 'SlidingWindow) [Double] IO where
readStore :: InMemoryStore 'SlidingWindow -> Text -> Text -> IO (Maybe [Double])
readStore (TimestampStore TVar (Map Text [Double])
tvar) Text
_prefix Text
key = forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
Map Text [Double]
stmMap <- forall a. TVar a -> STM a
readTVar TVar (Map Text [Double])
tvar
forall key value.
Hashable key =>
key -> Map key value -> STM (Maybe value)
StmMap.lookup Text
key Map Text [Double]
stmMap
writeStore :: InMemoryStore 'SlidingWindow
-> Text -> Text -> [Double] -> Int -> IO ()
writeStore (TimestampStore TVar (Map Text [Double])
tvar) Text
_prefix Text
key [Double]
val Int
_expiresIn = forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
Map Text [Double]
stmMap <- forall a. TVar a -> STM a
readTVar TVar (Map Text [Double])
tvar
forall key value.
Hashable key =>
value -> key -> Map key value -> STM ()
StmMap.insert [Double]
val Text
key Map Text [Double]
stmMap
deleteStore :: InMemoryStore 'SlidingWindow -> Text -> Text -> IO ()
deleteStore (TimestampStore TVar (Map Text [Double])
tvar) Text
_prefix Text
key = forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
Map Text [Double]
stmMap <- forall a. TVar a -> STM a
readTVar TVar (Map Text [Double])
tvar
forall key value. Hashable key => key -> Map key value -> STM ()
StmMap.delete Text
key Map Text [Double]
stmMap
instance CacheStore (InMemoryStore 'TokenBucket) TokenBucketState IO where
readStore :: InMemoryStore 'TokenBucket
-> Text -> Text -> IO (Maybe TokenBucketState)
readStore (TokenBucketStore TVar (Map Text TokenBucketEntry)
tvar) Text
_prefix Text
key = forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
Map Text TokenBucketEntry
stmMap <- forall a. TVar a -> STM a
readTVar TVar (Map Text TokenBucketEntry)
tvar
Maybe TokenBucketEntry
mval <- forall key value.
Hashable key =>
key -> Map key value -> STM (Maybe value)
StmMap.lookup Text
key Map Text TokenBucketEntry
stmMap
case Maybe TokenBucketEntry
mval of
Maybe TokenBucketEntry
Nothing -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a. Maybe a
Nothing
Just TokenBucketEntry
entry -> forall a. a -> Maybe a
Just forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. TVar a -> STM a
readTVar (TokenBucketEntry -> TVar TokenBucketState
tbeState TokenBucketEntry
entry)
writeStore :: InMemoryStore 'TokenBucket
-> Text -> Text -> TokenBucketState -> Int -> IO ()
writeStore (TokenBucketStore TVar (Map Text TokenBucketEntry)
tvar) Text
_prefix Text
key TokenBucketState
val Int
_expiresIn = do
TokenBucketEntry
entry <- TokenBucketState -> IO TokenBucketEntry
createTokenBucketEntry TokenBucketState
val
forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
Map Text TokenBucketEntry
stmMap <- forall a. TVar a -> STM a
readTVar TVar (Map Text TokenBucketEntry)
tvar
forall key value result.
Hashable key =>
Focus value STM result -> key -> Map key value -> STM result
StmMap.focus (forall entry v.
(entry -> TVar v) -> entry -> v -> Focus entry STM ()
focusInsertOrUpdate TokenBucketEntry -> TVar TokenBucketState
tbeState TokenBucketEntry
entry TokenBucketState
val) Text
key Map Text TokenBucketEntry
stmMap
deleteStore :: InMemoryStore 'TokenBucket -> Text -> Text -> IO ()
deleteStore (TokenBucketStore TVar (Map Text TokenBucketEntry)
tvar) Text
_prefix Text
key = forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
Map Text TokenBucketEntry
stmMap <- forall a. TVar a -> STM a
readTVar TVar (Map Text TokenBucketEntry)
tvar
forall key value. Hashable key => key -> Map key value -> STM ()
StmMap.delete Text
key Map Text TokenBucketEntry
stmMap
instance CacheStore (InMemoryStore 'LeakyBucket) LeakyBucketState IO where
readStore :: InMemoryStore 'LeakyBucket
-> Text -> Text -> IO (Maybe LeakyBucketState)
readStore (LeakyBucketStore TVar (Map Text LeakyBucketEntry)
tvar) Text
_prefix Text
key = forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
Map Text LeakyBucketEntry
stmMap <- forall a. TVar a -> STM a
readTVar TVar (Map Text LeakyBucketEntry)
tvar
Maybe LeakyBucketEntry
mval <- forall key value.
Hashable key =>
key -> Map key value -> STM (Maybe value)
StmMap.lookup Text
key Map Text LeakyBucketEntry
stmMap
case Maybe LeakyBucketEntry
mval of
Maybe LeakyBucketEntry
Nothing -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a. Maybe a
Nothing
Just LeakyBucketEntry
entry -> forall a. a -> Maybe a
Just forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. TVar a -> STM a
readTVar (LeakyBucketEntry -> TVar LeakyBucketState
lbeState LeakyBucketEntry
entry)
writeStore :: InMemoryStore 'LeakyBucket
-> Text -> Text -> LeakyBucketState -> Int -> IO ()
writeStore (LeakyBucketStore TVar (Map Text LeakyBucketEntry)
tvar) Text
_prefix Text
key LeakyBucketState
val Int
_expiresIn = do
LeakyBucketEntry
entry <- LeakyBucketState -> IO LeakyBucketEntry
createLeakyBucketEntry LeakyBucketState
val
forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
Map Text LeakyBucketEntry
stmMap <- forall a. TVar a -> STM a
readTVar TVar (Map Text LeakyBucketEntry)
tvar
forall key value result.
Hashable key =>
Focus value STM result -> key -> Map key value -> STM result
StmMap.focus (forall entry v.
(entry -> TVar v) -> entry -> v -> Focus entry STM ()
focusInsertOrUpdate LeakyBucketEntry -> TVar LeakyBucketState
lbeState LeakyBucketEntry
entry LeakyBucketState
val) Text
key Map Text LeakyBucketEntry
stmMap
deleteStore :: InMemoryStore 'LeakyBucket -> Text -> Text -> IO ()
deleteStore (LeakyBucketStore TVar (Map Text LeakyBucketEntry)
tvar) Text
_prefix Text
key = forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
Map Text LeakyBucketEntry
stmMap <- forall a. TVar a -> STM a
readTVar TVar (Map Text LeakyBucketEntry)
tvar
forall key value. Hashable key => key -> Map key value -> STM ()
StmMap.delete Text
key Map Text LeakyBucketEntry
stmMap
focusInsertOrUpdate
:: (entry -> TVar v)
-> entry
-> v
-> Focus.Focus entry STM ()
focusInsertOrUpdate :: forall entry v.
(entry -> TVar v) -> entry -> v -> Focus entry STM ()
focusInsertOrUpdate entry -> TVar v
getState entry
entry v
newVal = forall element (m :: * -> *) result.
m (result, Change element)
-> (element -> m (result, Change element))
-> Focus element m result
Focus.Focus
(forall (f :: * -> *) a. Applicative f => a -> f a
pure ((), forall a. a -> Change a
Focus.Set entry
entry))
(\entry
existing -> do
forall a. TVar a -> a -> STM ()
writeTVar (entry -> TVar v
getState entry
existing) v
newVal
forall (f :: * -> *) a. Applicative f => a -> f a
pure ((), forall a. Change a
Focus.Leave))
startLeakyBucketWorker
:: TVar LeakyBucketState
-> TQueue.TQueue (TMVar Bool)
-> Int
-> Double
-> IO ()
startLeakyBucketWorker :: TVar LeakyBucketState
-> TQueue (TMVar Bool) -> Int -> Double -> IO ()
startLeakyBucketWorker TVar LeakyBucketState
stateVar TQueue (TMVar Bool)
queue Int
capacity Double
leakRate = forall (f :: * -> *) a. Functor f => f a -> f ()
void forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO () -> IO ThreadId
forkIO forall a b. (a -> b) -> a -> b
$
forall (f :: * -> *) a b. Applicative f => f a -> f b
forever forall a b. (a -> b) -> a -> b
$ do
TMVar Bool
replyVar <- forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ forall a. TQueue a -> STM a
readTQueue TQueue (TMVar Bool)
queue
Double
now <- forall a b. (Real a, Fractional b) => a -> b
realToFrac forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO POSIXTime
getPOSIXTime
Bool
result <- forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
LeakyBucketState{Double
level :: LeakyBucketState -> Double
level :: Double
level,Double
lastTime :: LeakyBucketState -> Double
lastTime :: Double
lastTime} <- forall a. TVar a -> STM a
readTVar TVar LeakyBucketState
stateVar
let elapsed :: Double
elapsed = Double
now forall a. Num a => a -> a -> a
- Double
lastTime
leakedLevel :: Double
leakedLevel = forall a. Ord a => a -> a -> a
max Double
0 (Double
level forall a. Num a => a -> a -> a
- Double
elapsed forall a. Num a => a -> a -> a
* Double
leakRate)
nextLevel :: Double
nextLevel = Double
leakedLevel forall a. Num a => a -> a -> a
+ Double
1
allowed :: Bool
allowed = Double
nextLevel forall a. Ord a => a -> a -> Bool
<= forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
capacity
finalLevel :: Double
finalLevel = if Bool
allowed then Double
nextLevel else Double
leakedLevel
forall a. TVar a -> a -> STM ()
writeTVar TVar LeakyBucketState
stateVar LeakyBucketState{ level :: Double
level = Double
finalLevel, lastTime :: Double
lastTime = Double
now }
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
allowed
forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ forall a. TMVar a -> a -> STM ()
putTMVar TMVar Bool
replyVar Bool
result
instance CacheStore (InMemoryStore 'TinyLRU) Int IO where
readStore :: InMemoryStore 'TinyLRU -> Text -> Text -> IO (Maybe Int)
readStore (TinyLRUStore TVar (TinyLRUCache s)
ref) Text
_prefix Text
key = do
TimeSpec
now <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ Clock -> IO TimeSpec
getTime Clock
Monotonic
forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
TinyLRUCache s
cache <- forall a. TVar a -> STM a
readTVar TVar (TinyLRUCache s)
ref
Maybe (TVar (LRUNode s))
maybeNodeRef <- forall key value.
Hashable key =>
key -> Map key value -> STM (Maybe value)
StmMap.lookup Text
key (forall s. TinyLRUCache s -> Map Text (TVar (LRUNode s))
TinyLRU.lruCache TinyLRUCache s
cache)
case Maybe (TVar (LRUNode s))
maybeNodeRef of
Just TVar (LRUNode s)
nodeRef -> do
LRUNode s
node <- forall a. TVar a -> STM a
readTVar TVar (LRUNode s)
nodeRef
let expired :: Bool
expired = forall s. TimeSpec -> LRUNode s -> Bool
TinyLRU.isExpired TimeSpec
now LRUNode s
node
if Bool
expired
then do
forall s. Text -> TinyLRUCache s -> STM ()
TinyLRU.deleteKey Text
key TinyLRUCache s
cache
forall (m :: * -> *) a. Monad m => a -> m a
return forall a. Maybe a
Nothing
else do
let Maybe Int
decoded :: Maybe Int = forall a. FromJSON a => ByteString -> Maybe a
decodeStrict (forall s. LRUNode s -> ByteString
TinyLRU.nodeValue LRUNode s
node)
forall s. TinyLRUCache s -> TVar (LRUNode s) -> STM ()
TinyLRU.moveToFrontInCache TinyLRUCache s
cache TVar (LRUNode s)
nodeRef
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe Int
decoded
Maybe (TVar (LRUNode s))
Nothing -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a. Maybe a
Nothing
writeStore :: InMemoryStore 'TinyLRU -> Text -> Text -> Int -> Int -> IO ()
writeStore (TinyLRUStore TVar (TinyLRUCache s)
ref) Text
_prefix Text
key Int
val Int
expiresIn = do
TimeSpec
now <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ Clock -> IO TimeSpec
getTime Clock
Monotonic
forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
TinyLRUCache s
cache <- forall a. TVar a -> STM a
readTVar TVar (TinyLRUCache s)
ref
Maybe Int
_ <- forall a s.
(FromJSON a, ToJSON a) =>
TimeSpec -> Text -> a -> Int -> TinyLRUCache s -> STM (Maybe a)
TinyLRU.updateValue TimeSpec
now Text
key Int
val Int
expiresIn TinyLRUCache s
cache
forall (m :: * -> *) a. Monad m => a -> m a
return ()
deleteStore :: InMemoryStore 'TinyLRU -> Text -> Text -> IO ()
deleteStore (TinyLRUStore TVar (TinyLRUCache s)
ref) Text
_prefix Text
key = do
forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
TinyLRUCache s
cache <- forall a. TVar a -> STM a
readTVar TVar (TinyLRUCache s)
ref
forall s. Text -> TinyLRUCache s -> STM ()
TinyLRU.deleteKey Text
key TinyLRUCache s
cache
instance ResettableStore (InMemoryStore a) where
resetStore :: InMemoryStore a -> IO ()
resetStore (CounterStore TVar (Cache Text Text)
tvar) = TVar (Cache Text Text) -> IO ()
resetStoreWith TVar (Cache Text Text)
tvar
resetStore (TimestampStore TVar (Map Text [Double])
tvar) = forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
Map Text [Double]
stmMap <- forall a. TVar a -> STM a
readTVar TVar (Map Text [Double])
tvar
forall key value. Map key value -> STM ()
StmMap.reset Map Text [Double]
stmMap
resetStore (TokenBucketStore TVar (Map Text TokenBucketEntry)
tvar) = forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
Map Text TokenBucketEntry
stmMap <- forall a. TVar a -> STM a
readTVar TVar (Map Text TokenBucketEntry)
tvar
forall key value. Map key value -> STM ()
StmMap.reset Map Text TokenBucketEntry
stmMap
resetStore (LeakyBucketStore TVar (Map Text LeakyBucketEntry)
tvar) = forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
Map Text LeakyBucketEntry
stmMap <- forall a. TVar a -> STM a
readTVar TVar (Map Text LeakyBucketEntry)
tvar
forall key value. Map key value -> STM ()
StmMap.reset Map Text LeakyBucketEntry
stmMap
resetStore (TinyLRUStore TVar (TinyLRUCache s)
ref) = forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
TinyLRUCache s
cache <- forall a. TVar a -> STM a
readTVar TVar (TinyLRUCache s)
ref
forall s. TinyLRUCache s -> STM ()
TinyLRU.resetTinyLRU TinyLRUCache s
cache
resetStoreWith :: TVar (C.Cache Text Text) -> IO ()
resetStoreWith :: TVar (Cache Text Text) -> IO ()
resetStoreWith TVar (Cache Text Text)
tvar = do
Cache Text Text
newCache <- forall k v. Maybe TimeSpec -> IO (Cache k v)
C.newCache forall a. Maybe a
Nothing
forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ forall a. TVar a -> a -> STM ()
writeTVar TVar (Cache Text Text)
tvar Cache Text Text
newCache
makeCacheKey :: Algorithm
-> Text
-> Text
-> Text
makeCacheKey :: Algorithm -> Text -> Text -> Text
makeCacheKey Algorithm
algo Text
ipZone Text
userKey = Algorithm -> Text
algorithmPrefix Algorithm
algo forall a. Semigroup a => a -> a -> a
<> Text
":" forall a. Semigroup a => a -> a -> a
<> Text
ipZone forall a. Semigroup a => a -> a -> a
<> Text
":" forall a. Semigroup a => a -> a -> a
<> Text
userKey