From 9b179be3241f12f6a5047836291008b57c39367e Mon Sep 17 00:00:00 2001 From: riccardom Date: Wed, 17 Jun 2026 23:30:16 +0200 Subject: [PATCH] Defines an API for knowing if the SERVICE is running (regardless of up and down state) New() builds s.connecClient and is called when the gRPC service is started. Up() is invoked only IF a gRPC service IS running which is possible only if the New() was called. --- client/internal/connect.go | 12 ++++++++++++ client/server/server.go | 11 ++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/client/internal/connect.go b/client/internal/connect.go index 020d87b7d..7e444ad49 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -483,6 +483,18 @@ func (c *ConnectClient) IsRunning() bool { return c.sup.isRunning() } +// ServiceRunning reports whether the client's lifecycle supervisor is alive and +// able to accept start/stop commands — i.e. the daemon-lifetime client exists +// and its context has not been cancelled. It is independent of whether a run is +// currently up (that is IsRunning). Nil-safe, so callers can ask it on a +// not-yet-constructed client and treat false as "service not running". +func (c *ConnectClient) ServiceRunning() bool { + if c == nil || c.sup == nil { + return false + } + return c.sup.ctx.Err() == nil +} + func (c *ConnectClient) Engine() *Engine { if c == nil { return nil diff --git a/client/server/server.go b/client/server/server.go index fdbf9e625..7ea4cae3f 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -656,9 +656,14 @@ func (s *Server) WaitSSOLogin(callerCtx context.Context, msg *proto.WaitSSOLogin func (s *Server) Up(callerCtx context.Context, msg *proto.UpRequest) (*proto.UpResponse, error) { s.mutex.Lock() - // The client is built once in New(); a nil here means the service was never - // started. Fail loud rather than lazily creating it. - if s.connectClient == nil { + // The client (and its supervisor) is built once in New(), so a nil here + // never happens in production — Up is only reachable after New() has run and + // the gRPC server is serving. The real case this guards is the daemon + // SHUTTING DOWN: rootCtx is cancelled, the supervisor is no longer accepting + // commands, so ServiceRunning() is false even though the client exists. Bail + // loud instead of enqueuing a run that will never start. (nil only happens in + // tests that build a Server without New(); ServiceRunning is nil-safe.) + if !s.connectClient.ServiceRunning() { s.mutex.Unlock() return nil, fmt.Errorf("service is not running, start the netbird service for 'up' to take effect") }