Abstraction for throttling API calls

I am trying to write a convenient abstraction for thille api calls. The API call will be some kind of HTTP request. I want to be able to write all the logic of the application as if I were only in IOMonad, but then abstractions are suppressed by calls for me so that I do not exceed some predefined limits. If these calls were made asynchronously, it would be nice. I have it right now.

data APICallStats = APICallStats                                                  
    {                                                                             
      startTime     :: !UTCTime                                                   
    , requestCount  :: !Int                                                       
    , tps           :: !Int                                                       
    } deriving Show                                                               

newtype APICall a = APICall (IO a)                                                

initStats :: Int -> IO APICallStats                                               
initStats limit = APICallStats <$> getCurrentTime <*> pure 0 <*> pure limit         


makeCall :: MVar APICallStats -> APICall a -> IO a                                  
makeCall mv (APICall f) = do                                                      
    (APICallStats st c t) <- readMVar mv                                          
    now <- getCurrentTime                                                         

    let inSeconds = realToFrac (diffUTCTime now st) :: Double                     
        cp1 = fromIntegral (c+1)                                                  
        td = fromIntegral t                                                       
    when (cp1 / inSeconds > td)                                                   
        (threadDelay (round $ (cp1 / td - inSeconds)*1000000))                    

    modifyMVar_ mv
         (\(APICallStats start req ts) -> return $ APICallStats start (req+1) ts)

    f

And I can check it like this. And it works great in one or more threads because of MVar.

testCall :: String -> APICall ()                                                    
testCall id = APICall (getCurrentTime >>= (putStrLn . ((++) (id ++ " ")) . show)) 

test :: IO ()                                                                       
test = do                                                                           
    mv <- initStats 1 >>= newMVar                                                   
    forever $ makeCall mv (testCall "")                                             

threadedTest :: IO ()                                                               
threadedTest = do                                                                   
    mv <- initStats 1 >>= newMVar                                                   
    threadId <- forkIO $ forever $ makeCall mv (testCall "thread0")                 
    forever $ makeCall mv (testCall "main thread")                                  
    killThread threadId

, , . API, APICall a. , MonadIO, liftIO, . , , .

, , .

withThrottle :: Int -> StateT (MVar APICallStats) IO a -> IO a                      
withThrottle limit f = do                                                           
    mv <- initStats limit >>= newMVar                                               
    evalStateT f mv                                                                 

process :: APICall a -> StateT (MVar APICallStats) IO a                             
process a = do                                                                      
    mv <- get                                                                       
    liftIO $ makeCall mv a

- .

stateTest = do                                                                    
    withThrottle 2 $ do                                                           
        process (testCall "")                                                     
        process (testCall "")                                                     
        process (testCall "")                                                     
        liftIO $ threadDelay 10000000   -- Some long computation                                            
        process (testCall "")                                                       
        process (testCall "")                                                       
        process (testCall "")                                                       
        process (testCall "")

, , . , API . . - MonadIO. , , . , .

+4

Source: https://habr.com/ru/post/1532056/


All Articles