on master help sign in
more haddocks
commite2777a5009ba643af7620e434d452f46c4e50ce0 authorSaurabh Nanda <saurabh@vacationlabs.com> dateMon 11 May 2020, 16:50 (6 years ago) parentcca6a7a
3 files changed+146−62
src/OddJobs/ConfigBuilder.hs+12−10 src/OddJobs/Job.hs+44−21 src/OddJobs/Types.hs+90−31
src/OddJobs/ConfigBuilder.hs+12−10
@@ -31,6 +31,8 @@ import qualified System.Log.FastLogger as FLogger
3131--
3232-- It makes a few __important assumptions__ about your 'jobPayload 'JSON, which
3333-- are documented in 'defaultJobType'.
34+--
35+-- TODO: Link-off to tutorial.
3436mkConfig :: (LogLevel -> LogEvent -> IO ())
3537 -- ^ "Structured logging" function. Ref: 'cfgLogger'
3638 -> TableName
@@ -42,15 +44,15 @@ mkConfig :: (LogLevel -> LogEvent -> IO ())
4244 -> (Job -> IO ())
4345 -- ^ The actual "job runner" which contains your application code. Ref: 'cfgJobRunner'
4446 -> (Config -> Config)
45− -- ^ A function that allows you to modify the "interim config". The
46− -- "interim config" will cotains a bunch of in-built default values,
47− -- along with the table-name, db-pool, job-runner, etc) that you have
48− -- just provided. You can use this function to override the in-built
49− -- default values. If you do not wish to modify the "interim config"
50− -- just pass 'Prelude.id' as an argument to this parameter. __Note:__
51− -- it is stronly recommended that you __do not__ modify the generated
52− -- 'Config' outside of this function, unless you know what you're
53− -- doing.
47+ -- ^ A function that allows you to modify the \"interim config\". The
48+ -- \"interim config\" will cotain a bunch of in-built default config
49+ -- params, along with the config params that you\'ve just provided
50+ -- (i.e. logging function, table name, DB pool, etc). You can use this
51+ -- function to override values in the \"interim config\". If you do not
52+ -- wish to modify the \"interim config\" just pass 'Prelude.id' as an
53+ -- argument to this parameter. __Note:__ it is strongly recommended
54+ -- that you __do not__ modify the generated 'Config' outside of this
55+ -- function, unless you know what you're doing.
5456 -> Config
5557 -- ^ The final 'Config' that can be used to start various job-runners
5658mkConfig logger tname dbpool ccControl jrunner configOverridesFn =
@@ -77,7 +79,7 @@ mkConfig logger tname dbpool ccControl jrunner configOverridesFn =
7779
7880
7981
80− -- TODO; Should the library be doing this?
82+-- | TODO; Should the library be doing this?
8183defaultLogStr :: (Job -> Text)
8284 -> LogLevel
8385 -> LogEvent
src/OddJobs/Job.hs+44−21
@@ -31,6 +31,9 @@ module OddJobs.Job
3131 , TableName
3232 , delaySeconds
3333 , Seconds(..)
34+ , JobErrHandler(..)
35+ , AllJobTypes(..)
36+
3437 -- ** Structured logging
3538 --
3639 -- $logging
@@ -50,15 +53,15 @@ module OddJobs.Job
5053 -- * Database helpers
5154 --
5255 -- $dbHelpers
53− , findJobById
5456 , findJobByIdIO
55− , saveJob
5657 , saveJobIO
5758 , runJobNowIO
5859 , unlockJobIO
5960 , cancelJobIO
6061 , jobDbColumns
6162 , concatJobDbColumns
63+ , fetchAllJobTypes
64+ , fetchAllJobRunners
6265
6366 -- * JSON helpers
6467 --
@@ -67,11 +70,6 @@ module OddJobs.Job
6770 , throwParsePayload
6871 , eitherParsePayloadWith
6972 , throwParsePayloadWith
70−
71− , JobErrHandler(..)
72− , AllJobTypes(..)
73− , fetchAllJobTypes
74− , fetchAllJobRunners
7573 )
7674where
7775
@@ -147,20 +145,15 @@ class (MonadUnliftIO m, MonadBaseControl IO m) => HasJobRunner m where
147145
148146-- $logging
149147--
150−-- TODO: Complete the prose here
151−
152−
153−
154−
155−
156−
157−
158−-- $configHelpers
148+-- OddJobs uses \"structured logging\" for important events that occur during
149+-- the life-cycle of a job-runner. This is useful if you're using JSON\/XML for
150+-- aggegating logs of your entire application to something like Kibana, AWS
151+-- CloudFront, GCP StackDriver Logging, etc.
152+--
153+-- If you're not interested in using structured logging, look at
154+-- 'OddJobs.ConfigBuilder.defaultLogStr' to output plain-text logs (or you can
155+-- write your own function, as well).
159156--
160−-- #configHelpers#
161−
162−
163−
164157
165158
166159data RunnerEnv = RunnerEnv
@@ -219,6 +212,12 @@ jobWorkerName = do
219212 hname <- getHostName
220213 pure $ hname ++ ":" ++ (show pid)
221214
215+-- | If you are writing SQL queries where you want to return ALL columns from
216+-- the jobs table it is __recommended__ that you do not issue a @SELECT *@ or
217+-- @RETURNIG *@. List out specific DB columns using 'jobDbColumns' and
218+-- 'concatJobDbColumns' instead. This will insulate you from runtime errors
219+-- caused by addition of new columns to 'cfgTableName' in future versions of
220+-- OddJobs.
222221jobDbColumns :: (IsString s, Semigroup s) => [s]
223222jobDbColumns =
224223 [ "id"
@@ -233,6 +232,11 @@ jobDbColumns =
233232 , "locked_by"
234233 ]
235234
235+-- | All 'jobDbColumns' joined together with commas. Useful for constructing SQL
236+-- queries, eg:
237+--
238+-- @'query_' conn $ "SELECT " <> concatJobDbColumns <> "FROM jobs"@
239+
236240concatJobDbColumns :: (IsString s, Semigroup s) => s
237241concatJobDbColumns = concatJobDbColumns_ jobDbColumns ""
238242 where
@@ -251,6 +255,18 @@ withDbConnection action = do
251255 pool <- getDbPool
252256 withResource pool action
253257
258+--
259+-- $dbHelpers
260+--
261+-- A bunch of functions that help you query 'cfgTableName' and change the status
262+-- of individual jobs. Most of these functions are in @IO@ and you /might/ want
263+-- to write wrappers that lift them into you application's custom monad.
264+--
265+-- __Note:__ When passing a 'Connection' to these function, it is
266+-- __recommended__ __to not__ take a connection from 'cfgDbPool'. Use your
267+-- application\'s database pool instead.
268+--
269+
254270findJobById :: (HasJobRunner m)
255271 => JobId
256272 -> m (Maybe Job)
@@ -647,7 +663,8 @@ throwParsePayloadWith parser job =
647663 either throwString (pure . Prelude.id) (eitherParsePayloadWith parser job)
648664
649665
650−
666+-- | Used by the web\/admin UI to fetch a \"master list\" of all known
667+-- job-types. Ref: 'cfgAllJobTypes'
651668fetchAllJobTypes :: (MonadIO m)
652669 => Config
653670 -> m [Text]
@@ -657,6 +674,12 @@ fetchAllJobTypes Config{cfgAllJobTypes, cfgDbPool} = liftIO $ do
657674 AJTSql fn -> withResource cfgDbPool fn
658675 AJTCustom fn -> fn
659676
677+-- | Used by web\/admin IO to fetch a \"master list\" of all known job-runners.
678+-- There is a known issue with the way this has been implemented:
679+--
680+-- * Since this looks at the 'jobLockedBy' column of 'cfgTableName', it will
681+-- discover only those job-runners that are actively executing at least one
682+-- job at the time this function is executed.
660683fetchAllJobRunners :: (MonadIO m)
661684 => Config
662685 -> m [JobRunnerName]
src/OddJobs/Types.hs+90−31
@@ -60,14 +60,37 @@ data LogEvent
6060 | LogJobFailed !Job !SomeException !FailureMode !NominalDiffTime
6161 -- | Emitted when a job times out and is picked-up again for execution
6262 | LogJobTimeout !Job
63− -- | Emitted whenever 'jobPoller' polls the DB table
63+ -- | Emitted whenever 'OddJobs.Job.jobPoller' polls the DB table
6464 | LogPoll
6565 -- | Emitted whenever any other event occurs
6666 | LogText !Text
6767 deriving (Show, Generic)
6868
69−data FailureMode = FailWithRetry | FailPermanent deriving (Eq, Show)
70−
69+-- | Used by 'JobErrHandler' and 'LogEvent' to indicate the nature of failure.
70+data FailureMode
71+ -- The job failed, but will be retried in the future.
72+ = FailWithRetry
73+ -- | The job failed and will no longer be retried (probably because it has
74+ -- been tried 'cfgDefaultMaxAttempts' times already).
75+ | FailPermanent deriving (Eq, Show)
76+
77+-- | Exception handler for jobs. This is conceptually very similar to how
78+-- 'Control.Exception.Handler' and 'Control.Exception.catches' (from
79+-- 'Control.Exception') work in-tandem. Using 'cfgOnJobFailed' you can install
80+-- /multiple/ exception handlers, where each handler is responsible for one type
81+-- of exception. OddJobs will execute the correct exception handler on the basis
82+-- of the type of runtime exception raised. For example:
83+--
84+-- @
85+-- cfgOnJobFailed =
86+-- [ JobErrHandler $ \(e :: HttpException) job failMode -> ...
87+-- , JobErrHandler $ \(e :: SqlException) job failMode -> ...
88+-- , JobErrHandler $ \(e :: ) job failMode -> ...
89+-- ]
90+-- @
91+--
92+-- __TODO:__ Link-off to tutorial on how to use this to install airbrake
93+-- notifier.
7194data JobErrHandler a = forall e . (Exception e) => JobErrHandler (e -> Job -> FailureMode -> IO a)
7295
7396data ConcurrencyControl
@@ -96,12 +119,25 @@ instance Show ConcurrencyControl where
96119
97120type JobId = Int
98121
99−data Status = Success
100− | Queued
101− | Failed
102− | Retry
103− | Locked
104− deriving (Eq, Show, Generic, Enum, Bounded)
122+data Status
123+ -- | In the current version of odd-jobs you /should not/ find any jobs having
124+ -- the 'Success' status, because successful jobs are immediately deleted.
125+ -- However, in the future, we may keep them around for a certain time-period
126+ -- before removing them from the jobs table.
127+ = Success
128+ -- | Jobs in 'Queued' status /may/ be picked up by the job-runner on the basis
129+ -- of the 'jobRunAt' field.
130+ | Queued
131+ -- | Jobs in 'Failed' status will will not be retried by the job-runner.
132+ | Failed
133+ -- | Jobs in 'Retry' status will be retried by the job-runner on the basis of
134+ -- the 'jobRunAt' field.
135+ | Retry
136+ -- | Jobs in 'Locked' status are currently being executed by a job-runner,
137+ -- which is identified by the 'jobLockedBy' field. The start of job-execution
138+ -- is indicated by the 'jobLocketAt' field.
139+ | Locked
140+ deriving (Eq, Show, Generic, Enum, Bounded)
105141
106142instance Ord Status where
107143 compare x y = compare (toText x) (toText y)
@@ -174,27 +210,40 @@ instance FromRow Job where
174210-- completed failed.
175211type JobRunner = Job -> IO ()
176212
213+-- | The web\/admin UI needs to know a \"master list\" of all job-types to be
214+-- able to power the \"filter by job-type\" feature. This data-type helps in
215+-- letting odd-jobs know /how/ to get such a master-list. The function specified
216+-- by this type is run once when the job-runner starts (and stored in an
217+-- internal @IORef@). After that the list of job-types needs to be updated
218+-- manually by pressing the appropriate \"refresh\" link in the admin\/web UI.
177219data AllJobTypes
220+ -- | A fixed-list of job-types. If you don't want to increase boilerplate,
221+ -- consider using 'OddJobs.ConfigBuilder.defaultConstantJobTypes' which will
222+ -- automatically generate the list of available job-types based on a sum-type
223+ -- that represents your job payload.
178224 = AJTFixed [Text]
225+ -- | Construct the list of job-types dynamically by looking at the actual
226+ -- payloads in 'cfgTableName' (using an SQL query).
179227 | AJTSql (Connection -> IO [Text])
228+ -- | A custom 'IO' action for fetching the list of job-types.
180229 | AJTCustom (IO [Text])
181230
182−
183−
184−
185231-- | While odd-jobs is highly configurable and the 'Config' data-type might seem
186232-- daunting at first, it is not necessary to tweak every single configuration
187−-- parameter by hand. Please start-off by using the sensible defaults provided
188−-- by the [configuration helpers](#configHelpers), and tweaking
189−-- config parameters on a case-by-case basis.
233+-- parameter by hand.
234+--
235+-- __Recommendation:__ Please start-off by building a 'Config' by using the
236+-- 'OddJobs.ConfigBuilder.mkConfig' function (to get something with sensible
237+-- defaults) and then tweaking config parameters on a case-by-case basis.
190238data Config = Config
191239 { -- | The DB table which holds your jobs. Please note, this should have been
192240 -- created by the 'OddJobs.Migrations.createJobTable' function.
193241 cfgTableName :: TableName
194242
195− -- | The actualy "job-runner" that __you__ need to provide. Please look at
196− -- the examples/tutorials if your applicaton's code is not in the @IO@
197− -- monad.
243+ -- | The actualy "job-runner" that __you__ need to provide. If this function
244+ -- throws a runtime exception, the job will be retried
245+ -- 'cfgDefaultMaxAttempts' times. Please look at the examples/tutorials if
246+ -- your applicaton's code is not in the @IO@ monad.
198247 , cfgJobRunner :: Job -> IO ()
199248
200249 -- | The number of times a failing job is retried before it is considered is
@@ -227,10 +276,9 @@ data Config = Config
227276 -- | User-defined callback function that is called whenever a job succeeds.
228277 , cfgOnJobSuccess :: Job -> IO ()
229278
230− -- | User-defined callback function that is called whenever a job fails. This
231− -- does not indicate permanent failure and means the job will be retried. It
232− -- is a good idea to log the failures to Airbrake, NewRelic, Sentry, or some
233− -- other error monitoring tool.
279+ -- | User-defined error-handler that is called whenever a job fails (indicated
280+ -- by 'cfgJobRunner' throwing an unhandled runtime exception). Please refer to
281+ -- 'JobErrHandler' for documentation on how to use this.
234282 , cfgOnJobFailed :: forall a . [JobErrHandler a]
235283
236284 -- | User-defined callback function that is called whenever a job starts
@@ -252,24 +300,35 @@ data Config = Config
252300 -- 'cffJobType' and 'defaultLogStr'
253301 , cfgLogger :: LogLevel -> LogEvent -> IO ()
254302
255− -- | How to extract the "job type" from a 'Job'. Related: 'defaultJobType'
303+ -- | How to extract the "job type" from a 'Job'. If you are overriding this,
304+ -- please consider overriding 'cfgJobTypeSql' as well. Related:
305+ -- 'OddJobs.ConfigBuilder.defaultJobType'
256306 , cfgJobType :: Job -> Text
257307
308+ -- | How to extract the \"job type\" directly in SQL. There are many places,
309+ -- especially in the web\/admin UI, where we need to know a job's type
310+ -- directly in SQL (because transferrring the entire @payload@ column to
311+ -- Haskell, and then parsing it into JSON, and then applying the
312+ -- 'cfgJobType' function on it would be too inefficient). Ref:
313+ -- 'OddJobs.ConfigBuilder.defaultJobTypeSql' and 'cfgJobType'
314+ , cfgJobTypeSql :: PGS.Query
315+
258316 -- | How long can a job run after which it is considered to be "crashed" and
259317 -- picked up for execution again
260318 , cfgDefaultJobTimeout :: Seconds
261319
262320 -- | How to convert a list of 'Job's to a list of HTML fragments. This is
263321 -- used in the Web\/Admin UI. This function accepts a /list/ of jobs and
264− -- returns a /list/ of 'Html' fragments is because, in case, you need to
265− -- query another table to fetch some metadata (eg. convert a primary-key to
266− -- a human-readable name), you can do it efficiently instead of resulting in
267− -- an N+1 SQL bug. Ref: defaultJobToHtml
322+ -- returns a /list/ of 'Html' fragments, because, in case, you need to query
323+ -- another table to fetch some metadata (eg. convert a primary-key to a
324+ -- human-readable name), you can do it efficiently instead of resulting in
325+ -- an N+1 SQL bug. Ref: 'defaultJobToHtml'
268326 , cfgJobToHtml :: [Job] -> IO [Html ()]
269327
270− -- | How to get a list of all known job-types?
328+ -- | How to get a list of all known job-types? This is used by the
329+ -- Web\/Admin UI to power the \"filter by job-type\" functionality. The
330+ -- default value for this is 'OddJobs.ConfigBuilder.defaultDynamicJobTypes'
331+ -- which does a @SELECT DISTINCT payload ->> ...@ to get a list of job-types
332+ -- directly from the DB.
271333 , cfgAllJobTypes :: AllJobTypes
272−
273− -- | TODO
274− , cfgJobTypeSql :: PGS.Query
275334 }