package health import ( "context" "sync" "time" ) type Health struct { liveChecks []Checker readyChecks []Checker maxConcurrent int } func New(opts ...Option) *Health { cfg := newConfig(opts...) return &Health{ liveChecks: cfg.liveChecks, readyChecks: cfg.readyChecks, maxConcurrent: cfg.maxConcurrent, } } type Status string const ( StatusPass Status = "pass" StatusFail Status = "fail" ) func (s Status) String() string { return string(s) } type SingleCheckResult struct { Status Status Err error Time time.Time } type Result struct { Status Status Checks map[string][]SingleCheckResult } func (h *Health) CheckLive(ctx context.Context) Result { return h.check(ctx, h.liveChecks) } func (h *Health) CheckReady(ctx context.Context) Result { return h.check(ctx, h.readyChecks) } type singleCheckResultWithName struct { name string SingleCheckResult } func (h *Health) check(ctx context.Context, checks []Checker) Result { limiterCh := make(chan struct{}, h.maxConcurrent) checkCh := make(chan singleCheckResultWithName) var wg sync.WaitGroup for _, ch := range checks { wg.Add(1) go func(ch Checker) { defer wg.Done() limiterCh <- struct{}{} defer func() { <-limiterCh }() check := SingleCheckResult{ Status: StatusPass, Time: time.Now(), } if err := ch.Check(ctx); err != nil { check.Status = StatusFail check.Err = err } checkCh <- singleCheckResultWithName{ name: ch.Name(), SingleCheckResult: check, } }(ch) } go func() { wg.Wait() close(limiterCh) close(checkCh) }() res := Result{ Status: StatusPass, Checks: make(map[string][]SingleCheckResult, len(checks)), } for check := range checkCh { if check.Status == StatusFail { res.Status = StatusFail } res.Checks[check.name] = append(res.Checks[check.name], check.SingleCheckResult) } return res }