{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-| This module contains: * Functions to manipulate the state of the Supervisor record * Utility functions used for communication between threads * Public API utility functions -} module Control.Concurrent.Capataz.Internal.Util where import RIO import qualified RIO.HashMap as HashMap import qualified RIO.List as List import qualified RIO.Text as T import RIO.Time (getCurrentTime) import GHC.Conc (labelThread) import Control.Concurrent.Capataz.Internal.Types -- | Returns only the number of the ThreadId. getTidNumber :: ThreadId -> Maybe Text getTidNumber tid = case T.words $ tshow tid of (_ : tidNumber : _) -> Just tidNumber _ -> Nothing -------------------------------------------------------------------------------- -- | Internal functions that overwrites the GHC thread name, for increasing -- traceability on GHC internals. setProcessThreadName :: MonadIO m => WorkerId -> WorkerName -> m () setProcessThreadName workerId workerName = do tid <- myThreadId let workerIdentifier = T.unpack workerName <> "_" <> show workerId <> "_" <> maybe "" T.unpack (getTidNumber tid) liftIO $ labelThread tid workerIdentifier -- | Gets the 'ProcessId' of both a Worker or Supervisor process. getProcessId :: Process m -> ProcessId getProcessId process = case process of WorkerProcess Worker { workerId } -> workerId SupervisorProcess Supervisor { supervisorId } -> supervisorId -- | Gets a supervised 'Process' from a 'Supervisor' instance. fetchProcess :: MonadIO m => SupervisorEnv m -> ProcessId -> m (Maybe (Process m)) fetchProcess SupervisorEnv { supervisorProcessMap } processId = do processMap <- readIORef supervisorProcessMap case HashMap.lookup processId processMap of Just process -> return $ Just process _ -> return Nothing -- | Appends a new 'Process' to the 'Supervisor' existing process map. appendProcessToMap :: MonadIO m => SupervisorEnv m -> Process m -> m () appendProcessToMap SupervisorEnv { supervisorProcessMap } process = atomicModifyIORef' supervisorProcessMap (\processMap -> (appendProcess processMap, ())) where appendProcess = HashMap.alter (const $ Just process) (getProcessId process) -- | Removes a 'Process' from a 'Supervisor' existing process map. removeProcessFromMap :: MonadIO m => SupervisorEnv m -> ProcessId -> m () removeProcessFromMap SupervisorEnv { supervisorProcessMap } processId = atomicModifyIORef' supervisorProcessMap (\processMap -> maybe (processMap, ()) (const (HashMap.delete processId processMap, ())) (HashMap.lookup processId processMap) ) -- | Function to modify a 'Supervisor' process map using a pure function. resetProcessMap :: MonadIO m => SupervisorEnv m -> (ProcessMap m -> ProcessMap m) -> m () resetProcessMap SupervisorEnv { supervisorProcessMap } processMapFn = atomicModifyIORef' supervisorProcessMap (\processMap -> (processMapFn processMap, ())) -- | Function to get a snapshot of a 'Supervisor' process map. readProcessMap :: MonadIO m => SupervisorEnv m -> m (ProcessMap m) readProcessMap SupervisorEnv { supervisorProcessMap } = readIORef supervisorProcessMap -- | Returns all processes of a 'Supervisor' by 'ProcessTerminationOrder'. This -- is used on 'AllForOne' restarts and shutdown operations. sortProcessesByTerminationOrder :: ProcessTerminationOrder -> ProcessMap m -> [Process m] sortProcessesByTerminationOrder terminationOrder processMap = case terminationOrder of OldestFirst -> workers NewestFirst -> reverse workers where -- NOTE: dissambiguates workerCreationTime field processCreationTime (WorkerProcess Worker { workerCreationTime }) = workerCreationTime processCreationTime (SupervisorProcess Supervisor { supervisorCreationTime }) = supervisorCreationTime workers = List.sortBy (comparing processCreationTime) (HashMap.elems processMap) -------------------------------------------------------------------------------- -- | Returns the 'SupervisorStatus', this sub-routine will retry transaction -- until its associated 'Supervisor' has a status different from 'Initializing'. readSupervisorStatusSTM :: TVar SupervisorStatus -> STM SupervisorStatus readSupervisorStatusSTM statusVar = do status <- readTVar statusVar if status == Initializing then retrySTM else return status -- | Executes transaction that returns the 'SupervisorStatus'. readSupervisorStatus :: MonadIO m => SupervisorEnv m -> m SupervisorStatus readSupervisorStatus SupervisorEnv { supervisorStatusVar } = atomically $ readTVar supervisorStatusVar -- | Modifes the 'Supervisor' status. -- -- __IMPORTANT__ This is the only function that should be used for this purpose -- given it has the side-effect of notifying a status change via the -- 'notifyEvent' sub-routine, orginally given in the 'CapatazOption' record. writeSupervisorStatus :: MonadIO m => SupervisorEnv m -> SupervisorStatus -> m () writeSupervisorStatus SupervisorEnv { supervisorId, supervisorName, supervisorStatusVar, notifyEvent } newSupervisorStatus = do prevSupervisorStatus <- atomically $ do prevStatus <- readTVar supervisorStatusVar writeTVar supervisorStatusVar newSupervisorStatus return prevStatus eventTime <- getCurrentTime notifyEvent SupervisorStatusChanged { supervisorId = supervisorId , supervisorName = supervisorName , prevSupervisorStatus , newSupervisorStatus , eventTime } -- | Used from public API functions to send 'ControlAction' messages to a -- Supervisor thread loop. sendControlMsg :: SupervisorEnv m -> ControlAction m -> m () sendControlMsg SupervisorEnv { supervisorNotify } ctrlMsg = supervisorNotify (ControlAction ctrlMsg) -- | Used from public API functions to send ControlAction messages to the a -- Supervisor thread loop, it receives an IO sub-routine that expects an IO -- operation that blocks a thread until the message is done. sendSyncControlMsg :: MonadIO m => SupervisorEnv m -> (m () -> ControlAction m) -- ^ Blocking sub-routine used from the caller -> m () sendSyncControlMsg SupervisorEnv { supervisorNotify } mkCtrlMsg = do result <- newEmptyMVar supervisorNotify (ControlAction $ mkCtrlMsg (putMVar result ())) takeMVar result -- | Utility function to transform a 'CapatazOptions' record to a -- 'SupervisorOptions' record. capatazOptionsToSupervisorOptions :: CapatazOptions m -> SupervisorOptions m capatazOptionsToSupervisorOptions CapatazOptions {..} = SupervisorOptions {..} -- | Utility function to transform a 'SupervisorEnv' record to a -- 'ParentSupervisorEnv' record; used on functions where supervision of -- supervisors is managed. toParentSupervisorEnv :: SupervisorEnv m -> ParentSupervisorEnv m toParentSupervisorEnv SupervisorEnv { supervisorId, supervisorName, supervisorNotify, notifyEvent } = ParentSupervisorEnv {..}