Cache getAllLectures requests for 60 seconds

We do this because Squeak doesn't have a getLectureById endpoint yet.
This commit is contained in:
Paul Brinkmeier 2022-09-05 17:11:23 +02:00
parent bb9a0643f1
commit 28598ad2cd
3 changed files with 28 additions and 14 deletions

View File

@ -40,7 +40,7 @@ library
, blaze-html >=0.9 , blaze-html >=0.9
, req >=3.10 , req >=3.10
, text >=1.0 , text >=1.0
, time , time >=1.13
default-language: Haskell2010 default-language: Haskell2010
executable lisa-exe executable lisa-exe
@ -58,7 +58,7 @@ executable lisa-exe
, lisa , lisa
, req >=3.10 , req >=3.10
, text >=1.0 , text >=1.0
, time , time >=1.13
default-language: Haskell2010 default-language: Haskell2010
test-suite lisa-test test-suite lisa-test
@ -77,5 +77,5 @@ test-suite lisa-test
, lisa , lisa
, req >=3.10 , req >=3.10
, text >=1.0 , text >=1.0
, time , time >=1.13
default-language: Haskell2010 default-language: Haskell2010

View File

@ -26,7 +26,7 @@ dependencies:
- req >= 3.10 - req >= 3.10
- Spock >= 0.14 - Spock >= 0.14
- text >= 1.0 - text >= 1.0
- time - time >= 1.13
library: library:
source-dirs: src source-dirs: src

View File

@ -10,9 +10,11 @@ module Lisa
import Control.Concurrent (MVar, newMVar, putMVar, takeMVar) import Control.Concurrent (MVar, newMVar, putMVar, takeMVar)
import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Time.Clock (UTCTime, getCurrentTime, addUTCTime)
import Network.HTTP.Req (https) import Network.HTTP.Req (https)
import Text.Blaze.Html (Html) import Text.Blaze.Html (Html)
import Text.Blaze.Html.Renderer.Utf8 (renderHtml) import Text.Blaze.Html.Renderer.Utf8 (renderHtml)
import Text.Printf (printf)
import Web.Spock (SpockAction, SpockM, ActionCtxT, get, getState, lazyBytes, param, root, setHeader, var, (<//>)) import Web.Spock (SpockAction, SpockM, ActionCtxT, get, getState, lazyBytes, param, root, setHeader, var, (<//>))
import Web.Spock.Config (PoolOrConn(PCNoDatabase), SpockCfg, defaultSpockCfg) import Web.Spock.Config (PoolOrConn(PCNoDatabase), SpockCfg, defaultSpockCfg)
@ -36,27 +38,39 @@ instance SqueakCtx (SpockAction () () State) where
-- State -- State
data State = State data State = State
{ stateLectures :: MVar (Maybe [Lecture]) -- ^ Cached lectures { stateLectures :: MVar (UTCTime, [Lecture]) -- ^ Cached lectures with cache expiration time.
} }
-- | Get lectures from cache if they've been retrieved before or from Squeak. -- | Get lectures from cache if they've been retrieved before or from Squeak.
-- Uses 'MVar's for synchronisation: As long as each thread 'takeMVar's before it 'putMVar's, we're in the clear.
-- TODO: Evaluate lectures to NF before 'putMVar'ing them.
getAllLecturesCached :: SpockAction () () State (Either SqueakError [Lecture]) getAllLecturesCached :: SpockAction () () State (Either SqueakError [Lecture])
getAllLecturesCached = do getAllLecturesCached = do
lecturesMVar <- stateLectures <$> getState lecturesMVar <- stateLectures <$> getState
contents <- liftIO $ takeMVar lecturesMVar (expirationTime, lectures) <- liftIO $ takeMVar lecturesMVar
lectures' <- case contents of now <- liftIO getCurrentTime
Just lectures -> pure $ Right lectures (newContents, result) <- if now >= expirationTime
Nothing -> do then -- Cache expired => fetch lectures again.
liftIO $ putStrLn "Fetching lectures for the first time..." Squeak.getAllLectures >>= \case
Squeak.getAllLectures -- On error: Clear cache.
liftIO $ putMVar lecturesMVar $ either (const Nothing) Just lectures' Left err ->
pure lectures' pure ((now, []), Left err)
-- On success: Cache fetched lectures for 60 seconds.
Right lectures' -> do
let expirationTime' = addUTCTime 60 now
liftIO $ printf "Fetched %d lectures, valid until %s\n" (length lectures') (show expirationTime')
pure ((expirationTime', lectures'), Right lectures')
else -- Cache still valid => return it.
pure ((expirationTime, lectures), Right lectures)
liftIO $ putMVar lecturesMVar newContents
pure result
-- Exports -- Exports
mkConfig :: IO (SpockCfg () () State) mkConfig :: IO (SpockCfg () () State)
mkConfig = do mkConfig = do
lecturesMVar <- newMVar Nothing now <- getCurrentTime
lecturesMVar <- newMVar (now, [])
defaultSpockCfg () PCNoDatabase (State lecturesMVar) defaultSpockCfg () PCNoDatabase (State lecturesMVar)
app :: SpockM () () State () app :: SpockM () () State ()