@@ -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 }