v0.10.0 ce48ee3

health.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package httpserver
4
5import (
6	"context"
7	"net/http"
8	"sync"
9	"time"
10
11	"github.com/bnema/zerowrap"
12)
13
14// healthCacheTTL is how long the result of the checks is reused. The endpoint
15// is unauthenticated and the checks touch the disk and the store: a burst of
16// requests must not turn into a burst of work.
17const healthCacheTTL = 5 * time.Second
18
19// healthCheckTimeout bounds one run of the checks.
20const healthCheckTimeout = 5 * time.Second
21
22// healthHandler answers 200 "ok" when every check passes and 503 otherwise.
23// The failure is logged, never sent: the body stays the same for any cause.
24// The result, failure included, is kept for healthCacheTTL; concurrent
25// requests wait for the run in progress rather than starting their own.
26func healthHandler(checks []Check) http.HandlerFunc {
27	var (
28		mu      sync.Mutex
29		until   time.Time // the cached result is valid before this instant
30		healthy bool
31	)
32	return func(w http.ResponseWriter, r *http.Request) {
33		mu.Lock()
34		if !time.Now().Before(until) {
35			// The run belongs to every waiting request, not to this one: a
36			// client that goes away must not cache a failure.
37			ctx, cancel := context.WithTimeout(context.WithoutCancel(r.Context()), healthCheckTimeout)
38			healthy = runChecks(ctx, checks)
39			cancel()
40			until = time.Now().Add(healthCacheTTL)
41		}
42		ok := healthy
43		mu.Unlock()
44
45		w.Header().Set("Cache-Control", "no-store")
46		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
47		if !ok {
48			w.WriteHeader(http.StatusServiceUnavailable)
49			_, _ = w.Write([]byte("unavailable\n"))
50			return
51		}
52		_, _ = w.Write([]byte("ok\n"))
53	}
54}
55
56// runChecks runs the checks in order and stops at the first failure, which it
57// logs.
58func runChecks(ctx context.Context, checks []Check) bool {
59	for _, check := range checks {
60		if err := check(ctx); err != nil {
61			log := zerowrap.FromCtx(ctx)
62			log.Warn().Err(err).Msg("health check failed")
63			return false
64		}
65	}
66	return true
67}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 45

↑↓ moveenter openesc close