glue-core-0.8.0.0: test/Glue/ShutdownSpec.hs
{-# LANGUAGE ScopedTypeVariables #-}
module Glue.ShutdownSpec (spec) where
import Control.Concurrent (yield)
import Control.Concurrent.Async (async, wait)
import Glue.Clock
import Glue.Clock.Virtual
import Glue.Shutdown
import Test.Hspec
import UnliftIO.STM (atomically, newEmptyTMVarIO,
putTMVar, takeTMVar)
spec :: Spec
spec = do
describe "gracefulShutdown" $ do
it "Allows in-flight requests to drain cleanly within the grace period and rejects new requests" $ do
vc <- newVirtualClock
release <- newEmptyTMVarIO
let clock = virtualClock vc :: Clock IO
service (req :: Int) = do
atomically $ takeTMVar release
pure (req * 2)
(wrapped, handle) <- gracefulShutdown ((defaultShutdownOptions :: ShutdownOptions IO Int) { shutdownGracePeriodMs = 500, shutdownClock = clock }) service
isShuttingDown handle `shouldReturn` False
inFlight <- async (wrapped 21)
let waitInFlight = inFlightCount handle >>= \n -> if n == 1 then pure () else yield >> waitInFlight
waitInFlight
shutdown <- async (initiateShutdown handle)
waitForSleepers vc 1
isShuttingDown handle `shouldReturn` True
atomically $ putTMVar release ()
wait shutdown `shouldReturn` True
wait inFlight `shouldReturn` 42
wrapped 99 `shouldThrow` (== ServiceShuttingDownException "Service is shutting down.")
it "Returns shutdownFallback for rejected calls when configured" $ do
let opts = defaultShutdownOptions { shutdownFallback = Just (-1 :: Int), shutdownGracePeriodMs = 100 }
(wrapped, handle) <- gracefulShutdown opts (\x -> pure (x * 2))
wrapped 10 `shouldReturn` 20
_ <- initiateShutdown handle
wrapped 10 `shouldReturn` (-1)
it "Cancels stuck in-flight requests when grace period expires" $ do
vc <- newVirtualClock
let clock = virtualClock vc :: Clock IO
opts = (defaultShutdownOptions :: ShutdownOptions IO Int) { shutdownFallback = Just (-999), shutdownGracePeriodMs = 20, shutdownClock = clock }
slowService (_ :: Int) = clockSleep clock (500 * 1000) >> pure (100 :: Int)
(wrapped, handle) <- gracefulShutdown opts slowService
inFlight <- async (wrapped 1)
waitForSleepers vc 1
shutdown <- async (initiateShutdown handle)
waitForSleepers vc 2
advanceClock vc (20 * 1000)
wait shutdown `shouldReturn` False
wait inFlight `shouldReturn` (-999)
describe "gracefulShutdownEither" $
it "Returns the shutting-down failure as a value" $ do
(wrapped, handle) <- gracefulShutdownEither defaultShutdownOptions "shutting down" (\(x :: Int) -> pure (Right (x * 2)))
wrapped 4 `shouldReturn` (Right 8 :: Either String Int)
_ <- initiateShutdown handle
wrapped 4 `shouldReturn` Left "shutting down"