hammer-editor/server
Adam Brown d00bf6bde9
Add RecurringTask base for server background jobs (#701)
* Add RecurringTask base for server background jobs

Extract the shared start/stop/loop/exception-handling/shutdown scaffolding
that was copy-pasted across the three server background jobs into a single
RecurringTask base class plus a launchRecurringTask() Ktor wiring helper.

Subclasses now supply only the work (tick) and cadence (nextDelay), with an
optional errorBackoff override. Migrates TokenMaintenanceJob,
MonitoringMaintenanceJob, and PatreonPollingJob onto it and collapses their
three near-identical configure* functions.

Behavior notes:
- All three jobs now shut down gracefully via a suspending stop() that uses
  cancelAndJoin (previously only the monitoring job did; the others used a
  fire-and-forget cancel). No tick can outlive application shutdown.
- A failing tick is now retried after a fixed errorBackoff (default 1m;
  Patreon keeps its 5m). Previously the token job waited a full 24h before
  retrying after an error.

No new dependencies. Adds RecurringTaskTest covering the start guard, error
resilience, and graceful-stop contract; renames the monitoring lifecycle
test's stopAndJoin references to stop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9nnqWKfwSTeA38kzFXaM

* Add Background Jobs status panel to the admin dashboard

Surface each recurring task's health on the monitoring dashboard: whether
its loop is alive, when it last ran, and when it next runs.

Server:
- RecurringTask now records lastRun/nextRun and last-tick success/error from
  inside its loop (volatile, read via status()), using an injectable Clock
  (defaults to Clock.System; the monitoring job passes its own).
- New RecurringTaskRegistry collects started tasks; launchRecurringTask()
  registers each one. Registered as a Koin singleton.
- New /admin/monitoring/jobs page renders the registry's statuses, mirroring
  the existing monitoring panels (route handler + model map + Mustache),
  threaded through Frontend -> adminPage -> adminMonitoringPages.

UI:
- New admin-monitoring-jobs.mustache with an alive/stopped badge, last/next
  run times, a health banner, and per-job error detail; a "Jobs" entry in the
  monitoring sub-nav; supporting CSS and English message keys (other locales
  fall back to English automatically).

Notes:
- A task that never starts (e.g. Patreon polling when disabled at the server
  level) is not registered and so does not appear.

Tests: status timing + registry dedup/sort/idle behavior; existing route test
updated for the new parameter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9nnqWKfwSTeA38kzFXaM

* Fix flaky RecurringTask stop test

The in-flight tick awaited a CompletableDeferred, which is a cancellable
suspension point — so cancelAndJoin() interrupted it immediately and stop()
returned before the test's assertion, failing it.

Hold the tick in a NonCancellable block so stop() genuinely has to wait for
it, matching how a real tick runs past cancellation to its next cancellable
boundary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Provide RecurringTaskRegistry in Ktor route test Koin module

frontend() now injects RecurringTaskRegistry and passes it to adminPage at
route-setup time, so the Ktor test harness must define it. Add it to
setupKtorTestKoin alongside the other monitoring stand-ins; without it the
route tests fail with NoDefinitionFoundException.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-29 22:00:53 -07:00
..
src Add RecurringTask base for server background jobs (#701) 2026-06-29 22:00:53 -07:00
build.gradle.kts Use the HSTS plugin 2026-06-19 17:51:21 -07:00