1 {-# LANGUAGE MultiWayIf, FunctionalDependencies, MultiParamTypeClasses, TypeFamilies #-}
2 module Gargantext.Utils.Jobs.Monad (
10 -- * Tracking jobs status
29 import Gargantext.Utils.Jobs.Settings
30 import Gargantext.Utils.Jobs.Map
31 import Gargantext.Utils.Jobs.Queue
32 import Gargantext.Utils.Jobs.State
34 import Control.Concurrent.STM
35 import Control.Exception
36 import Control.Monad.Except
37 import Control.Monad.Reader
38 import Data.Kind (Type)
39 import Data.Map.Strict (Map)
40 import Data.Time.Clock
41 import qualified Data.Text as T
42 import Network.HTTP.Client (Manager)
45 import qualified Servant.Job.Core as SJ
46 import qualified Servant.Job.Types as SJ
48 data JobEnv t w a = JobEnv
49 { jeSettings :: JobSettings
50 , jeState :: JobsState t w a
51 , jeManager :: Manager
55 :: (EnumBounded t, Monoid w)
60 newJobEnv js prios mgr = JobEnv js <$> newJobsState js prios <*> pure mgr
64 defaultJobSettings :: NumRunners -> SJ.SecretKey -> JobSettings
65 defaultJobSettings numRunners k = JobSettings
66 { jsNumRunners = numRunners
67 , jsJobTimeout = 30 * 60 -- 30 minutes
68 , jsIDTimeout = 30 * 60 -- 30 minutes
69 , jsGcPeriod = 1 * 60 -- 1 minute
73 genSecret :: IO SJ.SecretKey
74 genSecret = SJ.generateSecretKey
76 class MonadIO m => MonadJob m t w a | m -> t w a where
77 getJobEnv :: m (JobEnv t w a)
79 instance MonadIO m => MonadJob (ReaderT (JobEnv t w a) m) t w a where
82 getJobsSettings :: MonadJob m t w a => m JobSettings
83 getJobsSettings = jeSettings <$> getJobEnv
85 getJobsState :: MonadJob m t w a => m (JobsState t w a)
86 getJobsState = jeState <$> getJobEnv
88 getJobsMap :: MonadJob m t w a => m (JobMap (SJ.JobID 'SJ.Safe) w a)
89 getJobsMap = jobsData <$> getJobsState
91 getJobsQueue :: MonadJob m t w a => m (Queue t (SJ.JobID 'SJ.Safe))
92 getJobsQueue = jobsQ <$> getJobsState
95 :: (MonadJob m t w a, Ord t)
98 -> (SJ.JobID 'SJ.Safe -> i -> Logger w -> IO a)
99 -> m (SJ.JobID 'SJ.Safe)
100 queueJob jobkind input f = do
101 js <- getJobsSettings
103 liftIO (pushJob jobkind input f js st)
108 -> m (Maybe (JobEntry (SJ.JobID 'SJ.Safe) w a))
111 liftIO $ lookupJob jid jmap
118 | JobException SomeException
123 => SJ.JobID 'SJ.Unsafe
124 -> m (Either JobError (SJ.JobID 'SJ.Safe))
125 checkJID (SJ.PrivateID tn n t d) = do
126 now <- liftIO getCurrentTime
127 js <- getJobsSettings
128 if | tn /= "job" -> return (Left InvalidIDType)
129 | now > addUTCTime (fromIntegral $ jsIDTimeout js) t -> return (Left IDExpired)
130 | d /= SJ.macID tn (jsSecretKey js) t n -> return (Left InvalidMacID)
131 | otherwise -> return $ Right (SJ.PrivateID tn n t d)
135 => SJ.JobID 'SJ.Unsafe
136 -> (SJ.JobID 'SJ.Safe -> JobEntry (SJ.JobID 'SJ.Safe) w a -> m r)
137 -> m (Either JobError (Maybe r))
141 Left e -> return (Left e)
145 Nothing -> return (Right Nothing)
146 Just j -> Right . Just <$> f jid' j
151 -> m (Either JobError a)
153 handleIDError toE act = act >>= \r -> case r of
154 Left err -> throwError (toE err)
158 :: (Ord t, MonadJob m t w a)
159 => Bool -- is it queued (and we have to remove jid from queue)
163 removeJob queued t jid = do
166 liftIO . atomically $ do
172 -- Tracking jobs status
175 -- | A monad to query for the status of a particular job /and/ submit updates for in-progress jobs.
176 class MonadJobStatus m where
178 -- | This is type family for the concrete 'JobHandle' that is associated to
179 -- a job when it starts and it can be used to query for its completion status. Different environment
180 -- can decide how this will look like.
181 type JobHandle m :: Type
183 type JobType m :: Type
184 type JobOutputType m :: Type
185 type JobEventType m :: Type
187 -- | Retrevies the latest 'JobEventType' from the underlying monad. It can be
188 -- used to query the latest status for a particular job, given its 'JobHandle' as input.
189 getLatestJobStatus :: JobHandle m -> m (JobEventType m)
191 -- | Adds an extra \"tracer\" that logs events to the passed action. Produces
192 -- a new 'JobHandle'.
193 withTracer :: Logger (JobEventType m) -> JobHandle m -> (JobHandle m -> m a) -> m a
197 -- | Start tracking a new 'JobEventType' with 'n' remaining steps.
198 markStarted :: Int -> JobHandle m -> m ()
200 -- | Mark 'n' steps of the job as succeeded, while simultaneously substracting this number
201 -- from the remaining steps.
202 markProgress :: Int -> JobHandle m -> m ()
204 -- | Mark 'n' step of the job as failed, while simultaneously substracting this number
205 -- from the remaining steps. Attach an optional error message to the failure.
206 markFailure :: Int -> Maybe T.Text -> JobHandle m -> m ()
208 -- | Finish tracking a job by marking all the remaining steps as succeeded.
209 markComplete :: JobHandle m -> m ()
211 -- | Finish tracking a job by marking all the remaining steps as failed. Attach an optional
212 -- message to the failure.
213 markFailed :: Maybe T.Text -> JobHandle m -> m ()