Repository navigation
Jobs
A Job is a row the scheduler re-runs every frequency seconds once next_run
is in the past. Creating one means choosing an operation from a fixed list in
the Jobs form. No Ruby, shell command, or other user-authored code is stored or
executed anywhere in DAD.
The script column holds the handler name, not code. Screenshots are from a
seeded demonstration dataset; see Interface.
This replaced the 2015 design.
Job#scriptused to hold arbitrary Ruby thatscheduler.rbran througheval, as whatever user the scheduler ran as — root under the previous deployment scripts. The recipe pages on this wiki were written against that design. See What Changed Since 2016.
JobHandler::HANDLERS is the whole set:
| operation | what it does |
|---|---|
prune_words |
trims the importer's in-memory word cache and re-tunes its lifetime (starts at 320s) |
reset_caches |
clears the importer's class-level caches (needed after a database rollback) |
reset_stats |
resets the throughput clock behind the events/sec chart |
disk_utilization |
reports usage of the partition holding the event archives |
maintain_event_partitions |
creates next month's partitions, archives and drops expired ones (Postgres only; seeded daily) |
tune_dashboard_feed |
persists the self-tuned live-feed size (seeded hourly) |
prune_sessions |
reclaims login sessions whose expiry has passed (seeded hourly) |
poll_windows_events |
polls every Windows host whose next_poll_at has arrived (seeded every 60s) |
db:seed creates the last four. The others exist for operators who want them on a
different cadence.
For poll_windows_events the two are deliberately different numbers. The Job's
frequency only sets how often the due-list is swept; the interval that decides
when any given host is polled is computed per host from its priority band and the
rate it is producing. So the Job runs often and cheaply, and does nothing when
nothing is due. See Windows Events.
The loop body lives in JobScheduler, not inline in scheduler.rb, so it can be
tested. A failing handler is rescued, raises a Critical alert, and the Job is
still rescheduled. A permanently broken Job therefore raises an alert on every
cycle forever — fix or delete the Job rather than acknowledging the alert
repeatedly.
A Job with no usable frequency is handled differently, and deliberately so.
frequency used to be an unvalidated integer, and the line that computed
next_run sat outside the begin/rescue. A blank Frequency field in the form
saved a nil, the scheduler raised undefined method 'seconds' for nil past the
rescue and out of the process, and because the crashing row's next_run never
advanced it was re-selected on every start. Under Restart=always that is a
permanent crash loop in which no Job runs at all — not just the broken one.
So now:
-
Jobvalidatesfrequencyas a positive integer, and the form cannot save a blank or non-positive one. - The column defaults to 3600, and migration
20261108000001rewrites any existing nil/zero/negative row to 3600 before adding aCHECK. TheCHECKis applied only on a PostgreSQL connection — the migration guards onadapter_name, because the constraint is added withALTER TABLE ADD CONSTRAINTand an existing database loaded from the portabledb/schema.rbdump has no such constraint to re-add. Model validation is the application-level enforcement either way. - If a row still has no usable interval,
JobScheduler.rescheduledisarms it:next_rungoes NULL, which thenext_run < ?predicate never matches, plus a Critical alert. It stops being selected instead of alerting once a minute forever. Set a positive interval to re-enable it.
Job validation also rejects a script that is not a registered name, and
JobHandler.perform raises UnknownHandler rather than falling back to eval.
An unrecognised name is treated as a data error (or a tampered row), not something
to interpret.
Adding capability means adding an entry to JobHandler::HANDLERS and deploying.
That is deliberate: it is a code change with a review, not something an operator
can do from a browser.
HANDLERS = {
# ...
'my_check' => ->(_arg) { MyModel.run_check; 'summary for the log' },
}Keep handlers idempotent — the scheduler re-runs a failing Job on the next cycle.
Add the name to db/seeds.rb too if a fresh install should run it by default;
SeedsIdempotencyTest asserts each seeded Job appears exactly once.
Each handler takes a single optional scalar argument and returns a String summary
that scheduler.rb writes to its log (journalctl -u dad-scheduler). The Jobs
screens do not display handler output — they show name, operation, owner,
last/next run and frequency.
If you are porting one of the Example Jobs on this wiki, that
handler is where the logic now belongs. The building blocks are unchanged:
Event.search, Alert.genericAlert, Alert.hostUnreachable,
System.with_events_within_hours, System#hourly_stats, Event.diskUtilization.
Event.recent_events self-tunes how many lines the live dashboard fetches, but it
runs on GET /events and must not write — a page being looked at used to
issue an UPDATE per poll per viewer. The tuned value therefore only reaches the
Preference row through the tune_dashboard_feed job. Without that job the feed
still works; it just never adapts to noisy Exclusions.
The Jobs screens are behind the Taskmaster right. See Access Control.