Documentation
¶
Overview ¶
Package grpc provides the GoCell gRPC server adapter.
Overview ¶
The adapter wraps a *grpc.Server and exposes the lifecycle.ManagedResource interface so bootstrap.WithManagedResource can manage its lifecycle (health probes, background worker, LIFO teardown).
Transport security ¶
Three modes are supported:
- mTLS (production path): supply CertPEM + KeyPEM + ClientCAPEM. The server requires and verifies client certificates via TLS 1.3 (tlsutil.NewServerMTLSConfig + tlsutil.NewClientCAPool).
- Server-side TLS (single direction): supply CertPEM + KeyPEM only. Clients authenticate the server; the server does not verify clients.
- Plaintext (dev or mesh-sidecar): set Config.TLS.AllowInsecure = true. The bind address is not restricted to loopback — a service-mesh sidecar terminating mTLS at the pod boundary and forwarding cleartext is a valid production posture. Omitting both AllowInsecure and PEM material is rejected fail-closed (the explicit opt-in is the only gate).
Interceptors ¶
This package registers zero interceptors. Recovery, metrics, tracing, and auth interceptors are composed at the bootstrap layer (PR-4) and plumbed in via grpc.ServerOption; they are not a concern of this adapter.
Service registration (PR-7: Form B callback, cell-facing) ¶
Services are registered via the cell-facing drain model introduced in PR-7:
- Cells declare GRPCServiceSpec in Cell.Init via reg.GRPCService(spec).
- bootstrap drains RegistrySnapshot.GRPCServices in phase7b and calls srv.Registrar().Register(spec) for each spec — AFTER binding the gRPC listener but BEFORE grpcServeAll.
- The Registrar intercepts the spec.Register callback's RegisterService call to record /{service}/{method}→cellID attribution.
Composition-root wiring (cmd/ or examples/):
chain := interceptor.NewUnaryChain(interceptor.Deps{...})
srv, _ := adaptersgrpc.New(adaptersgrpc.Config{..., ServerOptions: []grpc.ServerOption{chain}})
bootstrap.New(clk,
bootstrap.WithGRPCListener(cell.PrimaryListener, srv, ":9000"),
)
// Services are registered automatically from cell declarations — no manual
// srv.Registrar().Register(...) needed in the composition root.
Readiness probe ¶
ProbeReady ("grpc_ready") is healthy while grpcServer.Serve is active. The serving flag is set after grpcServer.Serve starts its accept loop, so there is a sub-millisecond window between flag flip and the first Accept. At typical readiness-probe polling intervals (≥100 ms) this window is invisible.
Scope boundary ¶
This adapter provides server-side lifecycle only. Client-side gRPC adapters and codegen/proto plumbing (PR-2/6) are delivered in other PRs.
Index ¶
Constants ¶
const ( // ErrAdapterGRPCConfigInvalid indicates a configuration validation failure // (missing address, conflicting TLS/insecure flags, missing cert/key). ErrAdapterGRPCConfigInvalid errcode.Code = "ERR_ADAPTER_GRPC_CONFIG_INVALID" // ErrAdapterGRPCTLSConfig indicates a TLS configuration build failure // (PEM parse error, cert/key mismatch, CA pool error). ErrAdapterGRPCTLSConfig errcode.Code = "ERR_ADAPTER_GRPC_TLS_CONFIG" // ErrAdapterGRPCListen indicates a net.Listen failure at server startup. ErrAdapterGRPCListen errcode.Code = "ERR_ADAPTER_GRPC_LISTEN" // ErrAdapterGRPCServe indicates a grpcServer.Serve failure (non-graceful). ErrAdapterGRPCServe errcode.Code = "ERR_ADAPTER_GRPC_SERVE" )
gRPC adapter error codes.
const ProbeReady healthz.ProbeName = "grpc_ready"
ProbeReady is the readiness probe name for the gRPC server, funneled through the PROBENAME-SEALED-FUNNEL-01 typed-const discipline. The probe reflects whether the server is actively serving (grpcServer.Serve has started and not yet returned). The "_ready" suffix follows the adapter dependency-availability naming convention from .claude/rules/gocell/observability.md.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// Addr is the listen address (e.g. ":9000"). Required.
Addr string
// ShutdownTimeout bounds the GracefulStop phase. Defaults to 30s.
ShutdownTimeout time.Duration
// TLS configures transport security. See TLSConfig for the three supported modes.
TLS TLSConfig
// ServerOptions are extra grpc.ServerOptions appended after the TLS
// credentials option. The composition root (runtime/bootstrap) uses this to
// inject the unary interceptor chain (runtime/grpc/interceptor.NewUnaryChain).
// It is not a business-bypass seam: cells/ cannot import adapters/ (layering
// rule), so only composition roots ever construct a Config.
ServerOptions []grpc.ServerOption
}
Config holds the gRPC server configuration.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is a gRPC server adapter implementing lifecycle.ManagedResource. It wraps a *grpc.Server with the GoCell lifecycle contract: Probes() / Worker() / Close().
Construction: call New(cfg) to obtain a configured *Server. Services are registered during bootstrap via Registrar().Register(spec) — cells declare GRPCServiceSpec in Cell.Init; bootstrap drains the snapshot and calls Registrar().Register for each spec in phase7b, before grpcServeAll.
Concurrency: all public methods are safe for concurrent use. GracefulStop is idempotent via stopOnce — calling Worker().Stop() and Close() simultaneously (LIFO teardown) is safe.
func New ¶
New creates a Server from cfg. It validates the config, builds TLS credentials (if configured), and constructs the underlying *grpc.Server. No network I/O is performed; binding happens when the Worker starts.
func (*Server) Close ¶
Close implements lifecycle.ManagedResource. It is equivalent to Worker().Stop() but may be called independently for LIFO teardown. Idempotent — safe to call multiple times or concurrently with Worker().Stop(). The second concurrent caller always returns nil even if the first caller's ctx expired (hard-stop path); this matches the LIFO bootstrap teardown contract.
func (*Server) Probes ¶
Probes implements lifecycle.ManagedResource. It returns a single probe named ProbeReady ("grpc_ready") that reports healthy (nil) when the server is actively serving and unhealthy when stopped or not yet started.
The serving flag is set immediately after grpcServer.Serve starts (before the first Accept). A sub-millisecond window exists between the flag flip and the server being ready to accept connections; at typical readiness-probe polling intervals this window is invisible in practice.
func (*Server) Registrar ¶
func (s *Server) Registrar() cell.GRPCServiceRegistrar
Registrar returns the cell-facing service registrar that bootstrap uses to register gRPC services (GAP-1 PR-7 [#1150]). It wraps the underlying *grpc.Server and intercepts RegisterService calls to record method→cellID attribution for each spec.
The return type is cell.GRPCServiceRegistrar (the narrow kernel-defined interface) so bootstrap's GRPCServer interface can reference it without importing adapters/grpc or google.golang.org/grpc. The concrete value is *runtime/grpc.ServiceRegistrar, which callers that import adapters/grpc may type-assert to access CellIDForMethod (PR-9).
All service registrations must happen before Serve/Worker().Start() to avoid data races — the bootstrap drain (phase7b) guarantees this ordering.
func (*Server) Serve ¶
Serve serves RPCs on the caller-supplied, already-bound listener until ctx is canceled or the server stops. It is the listener-injection counterpart of Worker().Start (which binds cfg.Addr internally): the composition root (runtime/bootstrap) pre-binds the socket synchronously — so port conflicts surface before any goroutine starts — and serves it here, exactly as the HTTP path calls http.Server.Serve(ln). Tests inject a bufconn listener the same way. Drain is driven by Close (graceful, bounded by cfg.ShutdownTimeout); ctx cancellation triggers the same drain as a fallback.
type TLSConfig ¶
type TLSConfig struct {
// AllowInsecure enables plaintext (no TLS). Explicit opt-in (dev or
// mesh-sidecar). Must not be combined with CertPEM, KeyPEM, or ClientCAPEM.
//
// The listen address is deliberately NOT restricted to loopback when
// AllowInsecure is set. Binding plaintext on a non-loopback address (e.g.
// 0.0.0.0) is a legitimate production posture behind a service-mesh sidecar
// that terminates mTLS at the pod boundary and forwards cleartext over the
// loopback-equivalent pod network. The fail-closed guarantee here is solely
// the explicit opt-in: plaintext is impossible unless the operator sets this
// flag (Config.validate V5 rejects the zero value). A loopback-only check
// would break the sidecar topology and is intentionally omitted.
//
// Serving plaintext on a non-loopback address emits a startup slog.Warn (once
// per Serve) flagging the posture — observability, not a gate. This is
// expected in the sidecar topology; see warnIfInsecureNonLoopback (server.go).
AllowInsecure bool
// CertPEM is the PEM-encoded server leaf certificate. Required for TLS/mTLS.
CertPEM []byte
// KeyPEM is the PEM-encoded server private key. Required for TLS/mTLS.
KeyPEM []byte
// ClientCAPEM is the PEM-encoded CA bundle for client certificate verification.
// Non-empty enables mTLS (RequireAndVerifyClientCert).
ClientCAPEM []byte
}
TLSConfig holds PEM-encoded TLS materials for the gRPC server.
Zero value TLSConfig{} is invalid and is rejected by Config.validate() with ErrAdapterGRPCConfigInvalid (V5 fail-closed — neither plaintext nor TLS).
Three modes are supported:
- Plaintext (dev or mesh-sidecar): set AllowInsecure = true; leave all PEM fields nil. The bind address is not restricted to loopback (see AllowInsecure field doc); the explicit opt-in is the only fail-closed gate.
- Server-side TLS: set CertPEM + KeyPEM; leave ClientCAPEM nil.
- Mutual TLS (mTLS): set CertPEM + KeyPEM + ClientCAPEM.
AllowInsecure and any PEM material are mutually exclusive (V2 validation). Omitting both AllowInsecure and PEM fields is rejected fail-closed (V5).