spark-server/internal/bootstrap/health.go
zhr ae9a71034d feat: implement Spark IM V3 modular monolith
Add HTTP /v1 MVC, TCP send path, groups with server-side member-avatar collage, object-storage upload, and friends/favorites around the architecture-v3 layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 19:52:11 +08:00

96 lines
2.2 KiB
Go

package bootstrap
import (
"context"
"sync/atomic"
"time"
"spark-server/internal/infrastructure/mysql"
redisx "spark-server/internal/infrastructure/redis"
tcp "spark-server/internal/transport/tcp"
)
const (
depOK = "ok"
depSkipped = "skipped"
depDown = "down"
depClosed = "closed"
)
// ProbeReport is /readyz data. LB should use HTTP 503 when Ready is false.
type ProbeReport struct {
Status string `json:"status"`
Accepting bool `json:"accepting"`
MySQL string `json:"mysql"`
Redis string `json:"redis"`
ObjectStorage string `json:"object_storage"`
TaskPool string `json:"taskpool"`
}
// Probe implements httpx.Health for liveness/readiness.
type Probe struct {
mysql *mysql.DB
redis *redisx.Client
objects pinger
pool tcp.TaskPool
accepting *atomic.Bool
requireDeps bool
}
func (p *Probe) Ready(ctx context.Context) (bool, any) {
if p == nil {
return true, ProbeReport{Status: depOK, Accepting: true, MySQL: depSkipped, Redis: depSkipped, ObjectStorage: depSkipped, TaskPool: depOK}
}
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
rep := ProbeReport{
Accepting: p.accepting != nil && p.accepting.Load(),
MySQL: p.checkStore(ctx, p.mysql),
Redis: p.checkStore(ctx, p.redis),
ObjectStorage: p.checkOptional(ctx, p.objects),
TaskPool: depOK,
}
ready := rep.Accepting
if p.pool != nil && p.pool.Closed() {
rep.TaskPool = depClosed
ready = false
}
if rep.MySQL == depDown || rep.Redis == depDown || rep.ObjectStorage == depDown {
ready = false
}
if ready {
rep.Status = depOK
} else {
rep.Status = "not_ready"
}
return ready, rep
}
type pinger interface {
Ready() bool
Ping(ctx context.Context) error
}
func (p *Probe) checkStore(ctx context.Context, s pinger) string {
if s == nil || !s.Ready() {
if p.requireDeps {
return depDown
}
return depSkipped
}
if err := s.Ping(ctx); err != nil {
return depDown
}
return depOK
}
func (p *Probe) checkOptional(ctx context.Context, s pinger) string {
if s == nil || !s.Ready() {
return depSkipped
}
if err := s.Ping(ctx); err != nil {
return depDown
}
return depOK
}