carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package edge
2
3import (
4 "net/http"
5 "time"
6
7 "github.com/carlosframework/platform/internal/lbhealth"
8)
9
10// The load balancer health answer (region HA spec 2 §6): GET or HEAD
11// /.carlos/lb-health on port 80, from any host. The network load
12// balancer's HTTP check sends the target's address as the Host header, so
13// the steering probe, which answers only for its own hostname, cannot
14// serve it. Every condition is this box's own; lbhealth has the list.
15
16// lbHealthFromEnv is the state the edge answers from: the process's
17// (lbhealth.Default) with CARLOS_REGION_HA on, and nil with it off, which
18// leaves the path to the ordinary :80 handling exactly as before. The
19// activator's sweep is the hosting loop's beat; an edge with no activator
20// (carlos edge) has none, and its answer is never 200.
21func lbHealthFromEnv(getenv func(string) string, act Activator) *lbhealth.State {
22 if !regionHAFromEnv(getenv) {
23 return nil
24 }
25 s := lbhealth.Default
26 if a, ok := act.(interface{ LastSweep() time.Time }); ok {
27 s.SetActivator(a.LastSweep)
28 }
29 return s
30}
31
32// lbHealthAnswer answers the health check when r is one and region HA is
33// on, reporting whether it did.
34func (e *Edge) lbHealthAnswer(w http.ResponseWriter, r *http.Request) bool {
35 if e.lbHealth == nil {
36 return false
37 }
38 return lbhealth.Serve(w, r, e.lbHealth, time.Now())
39}
40
41// servingProbeTimeout bounds servingPathLive's wait for the lock.
42var servingProbeTimeout = time.Second
43
44// servingPathLive is the edge beat's proof that the :443 request path is not
45// wedged. Watch's registry poll shares no lock with ServeHTTP (the route
46// table is an atomic pointer, read without one), so a poll alone would keep
47// beating over a deadlocked request path. The one lock every cacheable GET
48// and HEAD on :443 takes is the response cache's (GetFor, maybeStore), so
49// this takes and releases it, and answers false if that takes longer than
50// servingProbeTimeout. A probe still waiting from an earlier tick is not
51// stacked: the answer stays false until it lands. An edge with no cache has
52// no such lock, and the probe passes. Nothing is added to ServeHTTP.
53func (e *Edge) servingPathLive() bool {
54 c := e.cache
55 if c == nil {
56 return true
57 }
58 if !e.probing.CompareAndSwap(false, true) {
59 return false
60 }
61 done := make(chan struct{})
62 go func() {
63 c.mu.Lock()
64 c.mu.Unlock() //nolint:staticcheck // take and release is the probe
65 e.probing.Store(false)
66 close(done)
67 }()
68 t := time.NewTimer(servingProbeTimeout)
69 defer t.Stop()
70 select {
71 case <-done:
72 return true
73 case <-t.C:
74 return false
75 }
76}
77