content: -- | Modular wrapper around Posix signal handlers
  --
  -- This module is only included in the .cabal file when we are not on Windows.
  module Pos.Infra.Util.SigHandler
         ( Signal(..)
         , installHandler
         , uninstallAllHandlers
         ) where
  
  import           Universum
  
  import           Control.Concurrent (modifyMVar_, withMVar)
  import qualified Data.Map.Strict as Map
  import           System.IO.Unsafe (unsafePerformIO)
  import qualified System.Posix.Signals as Posix
  
  {-------------------------------------------------------------------------------
    Enumeratate signals
  
    (The unix package doesn't use an enumeration but just uses numbers.)
    We don't list all supported signals because these are not available on
    all platforms; instead, we just introduce them when we need them.
  -------------------------------------------------------------------------------}
  
  -- | POSIX signal
  data Signal = SigHUP deriving (Show, Eq, Ord)
  
  toPosixSignal :: Signal -> Posix.Signal
  toPosixSignal SigHUP = Posix.sigHUP
  
  {-------------------------------------------------------------------------------
    Internal but global state
  -------------------------------------------------------------------------------}
  
  -- | The old Posix handlers (before we installed our own)
  regOldHandlers :: MVar (Map Signal Posix.Handler)
  {-# NOINLINE regOldHandlers #-}
  regOldHandlers = unsafePerformIO $ newMVar Map.empty
  
  -- | All globally installed handlers (the ones we call from our Posix handler)
  regAllHandlers :: MVar (Map Signal [IO ()])
  {-# NOINLINE regAllHandlers #-}
  regAllHandlers = unsafePerformIO $ newMVar Map.empty
  
  -- | The actual Posix handler that gets installed and calls all other handlers
  delegationHandler :: Signal -> IO ()
  delegationHandler signal =
      sequence_ =<< Map.findWithDefault [] signal <$> readMVar regAllHandlers
  
  {-------------------------------------------------------------------------------
    Public API
  -------------------------------------------------------------------------------}
  
  -- | Install handler for given signal
  --
  -- When multiple handlers are registered they will be called in order of
  -- invocation.
  installHandler :: Signal -> IO () -> IO ()
  installHandler signal handler = do
      -- Install Posix handler if we need to
      modifyMVar_ regOldHandlers $ \oldHandlers ->
        if Map.member signal oldHandlers then
          -- already installed, nothing to do
          return oldHandlers
        else do
          oldHandler <- Posix.installHandler
            (toPosixSignal signal)
            (Posix.Catch $ delegationHandler signal)
            Nothing
          return $ Map.insert signal oldHandler oldHandlers
  
      -- Register the new handler
      modifyMVar_ regAllHandlers $ return . Map.alter aux signal
    where
      aux :: Maybe [IO ()] -> Maybe [IO ()]
      aux Nothing         = Just [handler]
      aux (Just handlers) = Just (handlers ++ [handler])
  
  -- | Restore all the old Posix handlers
  --
  -- Any registered handlers will simply not be called anymore
  uninstallAllHandlers :: IO ()
  uninstallAllHandlers =
      withMVar regOldHandlers $ \oldHandlers ->
          forM_ (Map.toList oldHandlers) $ \(signal, oldHandler) ->
              void $
              Posix.installHandler (toPosixSignal signal) oldHandler Nothing
  