{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
module Keter.RateLimiter.LeakyBucket
(
allowRequest
) where
import Control.Concurrent.STM
import Control.Monad (when)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Text (Text)
import Data.Time.Clock.POSIX (getPOSIXTime)
import Keter.RateLimiter.Types (LeakyBucketState(..))
import Keter.RateLimiter.Cache
import Keter.RateLimiter.AutoPurge (LeakyBucketEntry(..))
import qualified Focus as F
import qualified StmContainers.Map as StmMap
allowRequest
:: MonadIO m
=> Cache (InMemoryStore 'LeakyBucket)
-> Text
-> Text
-> Text
-> Int
-> Double
-> m Bool
allowRequest :: forall (m :: * -> *).
MonadIO m =>
Cache (InMemoryStore 'LeakyBucket)
-> Text -> Text -> Text -> Int -> Double -> m Bool
allowRequest Cache (InMemoryStore 'LeakyBucket)
cache Text
throttleName Text
ipZone Text
userKey Int
capacity Double
leakRate = IO Bool -> m Bool
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> m Bool) -> IO Bool -> m Bool
forall a b. (a -> b) -> a -> b
$ do
if Int
capacity Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
0 then Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False else do
Double
now <- POSIXTime -> Double
forall a b. (Real a, Fractional b) => a -> b
realToFrac (POSIXTime -> Double) -> IO POSIXTime -> IO Double
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO POSIXTime
getPOSIXTime
let fullKey :: Text
fullKey = Text -> Algorithm -> Text -> Text -> Text
makeCacheKey Text
throttleName Algorithm
LeakyBucket Text
ipZone Text
userKey
LeakyBucketStore TVar (Map Text LeakyBucketEntry)
tvBuckets = Cache (InMemoryStore 'LeakyBucket) -> InMemoryStore 'LeakyBucket
forall store. Cache store -> store
cacheStore Cache (InMemoryStore 'LeakyBucket)
cache
TMVar Bool
replyVar <- IO (TMVar Bool)
forall a. IO (TMVar a)
newEmptyTMVarIO
LeakyBucketEntry
newEntry <- LeakyBucketState -> IO LeakyBucketEntry
createLeakyBucketEntry (Double -> Double -> LeakyBucketState
LeakyBucketState Double
0 Double
now)
LeakyBucketEntry
entry <- STM LeakyBucketEntry -> IO LeakyBucketEntry
forall a. STM a -> IO a
atomically (STM LeakyBucketEntry -> IO LeakyBucketEntry)
-> STM LeakyBucketEntry -> IO LeakyBucketEntry
forall a b. (a -> b) -> a -> b
$ do
Map Text LeakyBucketEntry
buckets <- TVar (Map Text LeakyBucketEntry) -> STM (Map Text LeakyBucketEntry)
forall a. TVar a -> STM a
readTVar TVar (Map Text LeakyBucketEntry)
tvBuckets
Focus LeakyBucketEntry STM LeakyBucketEntry
-> Text -> Map Text LeakyBucketEntry -> STM LeakyBucketEntry
forall key value result.
Hashable key =>
Focus value STM result -> key -> Map key value -> STM result
StmMap.focus
((LeakyBucketEntry, Change LeakyBucketEntry)
-> (LeakyBucketEntry
-> (LeakyBucketEntry, Change LeakyBucketEntry))
-> Focus LeakyBucketEntry STM LeakyBucketEntry
forall (m :: * -> *) b a.
Monad m =>
(b, Change a) -> (a -> (b, Change a)) -> Focus a m b
F.cases (LeakyBucketEntry
newEntry, LeakyBucketEntry -> Change LeakyBucketEntry
forall a. a -> Change a
F.Set LeakyBucketEntry
newEntry)
(\LeakyBucketEntry
existing -> (LeakyBucketEntry
existing, Change LeakyBucketEntry
forall a. Change a
F.Leave)))
Text
fullKey Map Text LeakyBucketEntry
buckets
STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ TQueue (TMVar Bool) -> TMVar Bool -> STM ()
forall a. TQueue a -> a -> STM ()
writeTQueue (LeakyBucketEntry -> TQueue (TMVar Bool)
lbeQueue LeakyBucketEntry
entry) TMVar Bool
replyVar
Bool
started <- STM Bool -> IO Bool
forall a. STM a -> IO a
atomically (STM Bool -> IO Bool) -> STM Bool -> IO Bool
forall a b. (a -> b) -> a -> b
$ TMVar () -> () -> STM Bool
forall a. TMVar a -> a -> STM Bool
tryPutTMVar (LeakyBucketEntry -> TMVar ()
lbeWorkerLock LeakyBucketEntry
entry) ()
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
started (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
TVar LeakyBucketState
-> TQueue (TMVar Bool) -> Int -> Double -> IO ()
startLeakyBucketWorker
(LeakyBucketEntry -> TVar LeakyBucketState
lbeState LeakyBucketEntry
entry)
(LeakyBucketEntry -> TQueue (TMVar Bool)
lbeQueue LeakyBucketEntry
entry)
Int
capacity
Double
leakRate
STM Bool -> IO Bool
forall a. STM a -> IO a
atomically (STM Bool -> IO Bool) -> STM Bool -> IO Bool
forall a b. (a -> b) -> a -> b
$ TMVar Bool -> STM Bool
forall a. TMVar a -> STM a
takeTMVar TMVar Bool
replyVar