diff --git a/backend/runner/runner.go b/backend/runner/runner.go index e10a6971bc..c3ca8704b6 100644 --- a/backend/runner/runner.go +++ b/backend/runner/runner.go @@ -126,6 +126,7 @@ func Start(ctx context.Context, config Config) error { return rpc.Serve(ctx, config.Bind, rpc.GRPC(ftlv1connect.NewVerbServiceHandler, svc), rpc.HTTP("/", svc), + rpc.HealthCheck(svc.healthCheck), ) } @@ -484,3 +485,11 @@ func (s *Service) getDeploymentLogger(ctx context.Context, deploymentKey model.D sink := newDeploymentLogsSink(s.deploymentLogQueue) return log.FromContext(ctx).AddSink(sink).Attrs(attrs) } + +func (s *Service) healthCheck(writer http.ResponseWriter, request *http.Request) { + if s.state.Load() == ftlv1.RunnerState_RUNNER_ASSIGNED { + writer.WriteHeader(http.StatusOK) + return + } + writer.WriteHeader(http.StatusServiceUnavailable) +} diff --git a/internal/rpc/server.go b/internal/rpc/server.go index a61a290240..2f72d71bcb 100644 --- a/internal/rpc/server.go +++ b/internal/rpc/server.go @@ -24,6 +24,7 @@ const ShutdownGracePeriod = time.Second * 5 type serverOptions struct { mux *http.ServeMux reflectionPaths []string + healthCheck http.HandlerFunc } type Option func(*serverOptions) @@ -49,6 +50,12 @@ func PProf() Option { } } +func HealthCheck(check http.HandlerFunc) Option { + return func(options *serverOptions) { + options.healthCheck = check + } +} + // RawGRPC is a convenience function for registering a GRPC server with default options without Pingable. func RawGRPC[Iface, Impl any](constructor RawGRPCServerConstructor[Iface], impl Impl, options ...connect.HandlerOption) Option { return func(o *serverOptions) { @@ -75,16 +82,17 @@ type Server struct { func NewServer(ctx context.Context, listen *url.URL, options ...Option) (*Server, error) { opts := &serverOptions{ mux: http.NewServeMux(), + healthCheck: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }), } - opts.mux.Handle("/healthz", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - })) - for _, option := range options { option(opts) } + opts.mux.Handle("/healthz", opts.healthCheck) + // Register reflection services. reflector := grpcreflect.NewStaticReflector(opts.reflectionPaths...) opts.mux.Handle(grpcreflect.NewHandlerV1(reflector))