~bigbes/lethe

ref: 964d8022994f1c6260df9565e1882ebaaf15badc lethe/internal/platform/health/health.go -rw-r--r-- 2.8 KiB
964d8022 — Eugene Blikh test(lethe): register savedsearch repo+handler in e2e steward graph a month ago
                                                                                
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
// Package health defines a small Checker abstraction and a steward-managed
// Set that aggregates every registered Checker into a single readiness probe.
//
// A Checker is anything with a name and a Check(ctx) method. Concrete checks
// live next to the subsystem they probe (DBCheck here, future probes such as
// IndexCheck or QueueCheck land in their own files). Each check is registered
// as a steward asset; Set multi-injects them via the `inject:""` tag so adding
// a probe is a registration-only operation — no edits to Set or Run.
//
// The handler that exposes /readyz lives in the HTTP layer (Phase 5) and
// consumes (*Set).Run directly.
package health

import (
	"context"
	"time"

	"sourcecraft.dev/bigbes/lethe/internal/platform/database"
)

// perCheckTimeout bounds the time any single Checker.Check call is allowed to
// take. The HTTP /readyz handler typically has its own request budget; the
// per-check timeout here is the inner bound so a hung check cannot starve the
// rest of the set.
const perCheckTimeout = 2 * time.Second

// Checker is the contract every health probe satisfies. Implementations may
// be steward services with their own dependencies (see DBCheck).
type Checker interface {
	Name() string
	Check(ctx context.Context) error
}

// Set is the steward-managed aggregator. It multi-injects every registered
// Checker via the `inject:""` tag. Adding a new check is a matter of
// registering an asset that implements Checker — no edits to Set are needed.
type Set struct {
	Checks []Checker `inject:""`
}

// Run invokes every registered Checker sequentially, applying a per-check
// timeout via context.WithTimeout. Returns a map keyed by Checker.Name() with
// the error each check produced (nil on success), and an aggregate allOK
// flag that is true iff every check succeeded.
//
// Empty Checks slice → empty map and allOK=true. This is intentional: an
// empty set means no subsystem has declared a readiness signal yet, not that
// the system is unhealthy.
func (s *Set) Run(ctx context.Context) (map[string]error, bool) {
	results := make(map[string]error, len(s.Checks))
	allOK := true
	for _, c := range s.Checks {
		ctx2, cancel := context.WithTimeout(ctx, perCheckTimeout)
		err := c.Check(ctx2)
		cancel()
		results[c.Name()] = err
		if err != nil {
			allOK = false
		}
	}
	return results, allOK
}

// DBCheck verifies the SQLite database is reachable by pinging the
// underlying *sql.DB. It is a steward service: register it as an asset and
// it will be picked up by Set's multi-injection.
type DBCheck struct {
	DB *database.Database `inject:""`
}

// Name returns the stable identifier surfaced in the readiness response.
func (c *DBCheck) Name() string { return "database" }

// Check pings the database. The 2s timeout is supplied by Set.Run.
func (c *DBCheck) Check(ctx context.Context) error {
	return c.DB.DB.PingContext(ctx)
}