Commit fd38b3b
authored
fix(health): report degraded while a configured job is not scheduled, and finish shutdown (#781)
Closes #780. Follows from the #773 discussion: the daemon should not
refuse to start on a rejected job, but it should stop claiming to be
fine.
## The check could not see the failure it existed to report
```go
func (hc *HealthChecker) checkScheduler() {
check := HealthCheck{Name: "scheduler", Status: HealthStatusHealthy,
Message: "Scheduler is operational"}
// In a real implementation, this would check the actual scheduler
// For now, we'll assume it's healthy if the service is running
```
A daemon with a mistyped schedule — a job that never fires — served a
green `/health`, which is the probe `docs/INTEGRATION_PATTERNS.md` tells
operators to point a container healthcheck at. After #777 the rejection
is on stderr at startup, and nothing a monitoring system polls reflected
it. Log lines get read once someone already suspects a problem.
## Where the state lives, and why there
The scheduler records the jobs it refuses, keyed by name with the
reason.
Recording it in the scheduler rather than in the config loader is what
makes this cover **jobs added long after startup** from Docker labels
(`--docker-poll-interval`), not only those read from the INI at boot —
every path that adds a job goes through `AddJob`. A name that later
registers clears its own entry, as does removing the job, so a corrected
config reloaded at runtime recovers **without a restart** instead of
staying degraded until someone notices.
## Degraded, not unhealthy
Per the comment at `health.go:24`, only `unhealthy` makes `/ready`
answer 503. Taking a daemon out of rotation because one job of twenty
has a typo would trade a silent failure for a louder one. `degraded`
appears in the body of `/health` and `/healthz`, where an operator or an
alert rule can act on it, while the jobs that do work keep running.
## Verified against a running daemon
One good job and one unschedulable one:
```json
{ "status": "degraded",
"checks": { "scheduler": {
"status": "degraded",
"message": "1 configured job(s) are not scheduled and will not run: broken" } } }
```
`/ready` still answers **200**. With both jobs valid it reports
`healthy` with the original message.
A checker constructed without a scheduler reports `degraded` rather than
`healthy` — claiming health it has not established is the habit that
made the old stub useless. That widens `NewHealthChecker` by one
parameter; the callers in tests pass `nil`.
## Test plan
- [x] `go test ./...` — green
- [x] `go test -race -tags=e2e ./e2e/...` — green
- [x] `golangci-lint run` incl. `--build-tags="e2e unix"` — 0 issues
- [x] `lefthook run pre-push` — exit 0
- [x] Both directions checked against the real binary and its HTTP
endpoints, including that `/ready` stays 200
- [x] Recovery covered: a refused job that later registers clears the
complaint
---
## Follow-up from review
Addressing the review found two further defects, both in the same family
as the one above — a mechanism that looked like it worked because
nothing checked whether it did.
### The health checker's loop could not be stopped
`runPeriodicChecks` ran `for range ticker.C` with no exit. Harmless for
the daemon's single instance, a leaked goroutine per case in tests.
`Stop()` ends it, and the daemon calls it via a shutdown hook.
### Shutdown ended the process before its hooks had run
Wiring that hook up showed it never ran. The daemon ended the process on
`ShutdownChan`, which is closed when shutdown **starts**:
```go
<-c.shutdownManager.ShutdownChan()
// Give some time for graceful shutdown to complete <- it does not
c.closeDone()
```
So only the first priority group ever executed. The web server has a
hook registered to stop it gracefully, and that hook was killed
mid-flight along with any request in progress. Measured on a release
build, `SIGTERM` reached the end of shutdown in **0 of 5 runs**; with
the fix, **5 of 5**.
The e2e test that covers shutdown asserted on the substring `"graceful
shutdown"` — which also matches `"Starting graceful shutdown"`, logged
*before* the first hook. That is why it stayed green. It now requires
the completion line.
One caveat worth stating plainly: the e2e harness builds with `-race`,
and the instrumentation reliably flips this race (5 of 5 runs complete
even on the broken code). So the strengthened e2e assertion would
**not** have caught this regression by itself. The deterministic guard
is `TestShutdownDoneClosesOnlyAfterEveryHookRan` in `core`.
### `/health` reported a version of `1.0.0` for every build
Hardcoded at the call site, so the endpoint could not be used to tell
which ofelia was answering. It now reports the build's version, or `dev`
when there are no ldflags.
## Verification of the follow-up
```
$ ofelia daemon --config=… (log-level=debug, web enabled), then SIGTERM
Executing shutdown hook: scheduler (priority: 10) ... completed successfully
Executing shutdown hook: http-server (priority: 20) ... completed successfully
Executing shutdown hook: health-checker (priority: 30) ... completed successfully
Graceful shutdown completed successfully
```
Before the fix, that log ended after the `scheduler` hook.13 files changed
Lines changed: 446 additions & 26 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
163 | 163 | | |
164 | 164 | | |
165 | 165 | | |
166 | | - | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
167 | 185 | | |
168 | 186 | | |
169 | 187 | | |
| |||
264 | 282 | | |
265 | 283 | | |
266 | 284 | | |
267 | | - | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
268 | 291 | | |
269 | | - | |
270 | | - | |
271 | | - | |
| 292 | + | |
272 | 293 | | |
273 | 294 | | |
274 | 295 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
61 | | - | |
62 | | - | |
63 | | - | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
64 | 70 | | |
65 | 71 | | |
66 | 72 | | |
| |||
342 | 348 | | |
343 | 349 | | |
344 | 350 | | |
| 351 | + | |
345 | 352 | | |
346 | 353 | | |
347 | 354 | | |
| |||
365 | 372 | | |
366 | 373 | | |
367 | 374 | | |
| 375 | + | |
368 | 376 | | |
369 | 377 | | |
370 | 378 | | |
371 | 379 | | |
372 | 380 | | |
373 | 381 | | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
374 | 386 | | |
375 | 387 | | |
376 | 388 | | |
| |||
408 | 420 | | |
409 | 421 | | |
410 | 422 | | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
411 | 426 | | |
412 | 427 | | |
413 | 428 | | |
| |||
635 | 650 | | |
636 | 651 | | |
637 | 652 | | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
638 | 681 | | |
639 | 682 | | |
640 | 683 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| 25 | + | |
25 | 26 | | |
26 | 27 | | |
27 | 28 | | |
| |||
43 | 44 | | |
44 | 45 | | |
45 | 46 | | |
| 47 | + | |
46 | 48 | | |
47 | 49 | | |
48 | 50 | | |
| |||
89 | 91 | | |
90 | 92 | | |
91 | 93 | | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
92 | 98 | | |
93 | 99 | | |
94 | 100 | | |
| |||
181 | 187 | | |
182 | 188 | | |
183 | 189 | | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
184 | 199 | | |
185 | 200 | | |
186 | 201 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
32 | | - | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
33 | 42 | | |
34 | 43 | | |
35 | 44 | | |
| |||
64 | 73 | | |
65 | 74 | | |
66 | 75 | | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
67 | 82 | | |
68 | 83 | | |
69 | 84 | | |
70 | | - | |
| 85 | + | |
71 | 86 | | |
72 | 87 | | |
73 | 88 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
106 | 106 | | |
107 | 107 | | |
108 | 108 | | |
109 | | - | |
| 109 | + | |
110 | 110 | | |
111 | 111 | | |
112 | 112 | | |
| |||
0 commit comments