package edge import ( "net/http" "time" "github.com/carlosframework/platform/internal/lbhealth" ) // The load balancer health answer (region HA spec 2 ยง6): GET or HEAD // /.carlos/lb-health on port 80, from any host. The network load // balancer's HTTP check sends the target's address as the Host header, so // the steering probe, which answers only for its own hostname, cannot // serve it. Every condition is this box's own; lbhealth has the list. // lbHealthFromEnv is the state the edge answers from: the process's // (lbhealth.Default) with CARLOS_REGION_HA on, and nil with it off, which // leaves the path to the ordinary :80 handling exactly as before. The // activator's sweep is the hosting loop's beat; an edge with no activator // (carlos edge) has none, and its answer is never 200. func lbHealthFromEnv(getenv func(string) string, act Activator) *lbhealth.State { if !regionHAFromEnv(getenv) { return nil } s := lbhealth.Default if a, ok := act.(interface{ LastSweep() time.Time }); ok { s.SetActivator(a.LastSweep) } return s } // lbHealthAnswer answers the health check when r is one and region HA is // on, reporting whether it did. func (e *Edge) lbHealthAnswer(w http.ResponseWriter, r *http.Request) bool { if e.lbHealth == nil { return false } return lbhealth.Serve(w, r, e.lbHealth, time.Now()) } // servingProbeTimeout bounds servingPathLive's wait for the lock. var servingProbeTimeout = time.Second // servingPathLive is the edge beat's proof that the :443 request path is not // wedged. Watch's registry poll shares no lock with ServeHTTP (the route // table is an atomic pointer, read without one), so a poll alone would keep // beating over a deadlocked request path. The one lock every cacheable GET // and HEAD on :443 takes is the response cache's (GetFor, maybeStore), so // this takes and releases it, and answers false if that takes longer than // servingProbeTimeout. A probe still waiting from an earlier tick is not // stacked: the answer stays false until it lands. An edge with no cache has // no such lock, and the probe passes. Nothing is added to ServeHTTP. func (e *Edge) servingPathLive() bool { c := e.cache if c == nil { return true } if !e.probing.CompareAndSwap(false, true) { return false } done := make(chan struct{}) go func() { c.mu.Lock() c.mu.Unlock() //nolint:staticcheck // take and release is the probe e.probing.Store(false) close(done) }() t := time.NewTimer(servingProbeTimeout) defer t.Stop() select { case <-done: return true case <-t.C: return false } }