8000 Jobs · dhoelzer/DAD Wiki · GitHub
[go: up one dir, main page]

Skip to content
openhands edited this page Nov 11, 2026 · 7 revisions

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.

Jobs

New job

The script column holds the handler name, not code. Screenshots are from a seeded demonstration dataset; see Interface.

This replaced the 2015 design. Job#script used to hold arbitrary Ruby that scheduler.rb ran through eval, 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.

The registered operations

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.

The Job frequency is not the polling interval

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.

Failure behaviour

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:

  • Job validates frequency as a positive integer, and the form cannot save a blank or non-positive one.
  • The column defaults to 3600, and migration 20261108000001 rewrites any existing nil/zero/negative row to 3600 before adding a CHECK. The CHECK is applied only on a PostgreSQL connection — the migration guards on adapter_name, because the constraint is added with ALTER TABLE ADD CONSTRAINT and an existing database loaded from the portable db/schema.rb dump 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.reschedule disarms it: next_run goes NULL, which the next_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 a new operation

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.

Jobs and the dashboard feed

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.

Permissions

The Jobs screens are behind the Taskmaster right. See Access Control.

Clone this wiki locally

0