packages feed

glue-core-0.7.0: test/Glue/ShutdownSpec.hs

{-# LANGUAGE ScopedTypeVariables #-}

module Glue.ShutdownSpec (spec) where

import           Control.Concurrent       (threadDelay)
import           Control.Concurrent.Async (async, wait)
import           Glue.Shutdown
import           Test.Hspec

spec :: Spec
spec = do
  describe "gracefulShutdown" $ do
    it "Allows in-flight requests to drain cleanly within the grace period and rejects new requests" $ do
      let service (req :: Int) = do
            threadDelay (30 * 1000)
            pure (req * 2)
      (wrapped, handle) <- gracefulShutdown (defaultShutdownOptions { shutdownGracePeriodMs = 500 }) service
      isShuttingDown handle `shouldReturn` False
      inFlight <- async (wrapped 21)
      threadDelay (5 * 1000)
      inFlightCount handle `shouldReturn` 1
      drainedCleanly <- initiateShutdown handle
      drainedCleanly `shouldBe` True
      isShuttingDown handle `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
      let opts = defaultShutdownOptions { shutdownFallback = Just (-999 :: Int), shutdownGracePeriodMs = 20 }
          slowService (_ :: Int) = threadDelay (500 * 1000) >> pure 100
      (wrapped, handle) <- gracefulShutdown opts slowService
      inFlight <- async (wrapped 1)
      threadDelay (5 * 1000)
      drainedCleanly <- initiateShutdown handle
      drainedCleanly `shouldBe` False
      wait inFlight `shouldReturn` (-999)