Documentation
¶
Overview ¶
Package connect manages a pool of reusable gRPC client connections keyed by a caller-supplied logical key, distinct from the dial target. Use NewPool to create a Pool, Set to register a connection for a key, Conn to retrieve it, ConnOrCreate to retrieve or create one on first use, and Close to shut every connection down exactly once.
Conn wraps that pool as a grpc.ClientConnInterface whose dial target is chosen per call by a Resolver: StaticResolver for a fixed upstream, or a caller-supplied dynamic Resolver that varies the target with the request.
Creating a connection does not open one, since gRPC connects on demand. Use WaitReady to open a fixed-target Conn (or several at once) before serving traffic, so an unreachable target is reported up front rather than as a request error later.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrDuplicateKey is returned by Pool.Set when a connection is already // registered for the given key. ErrDuplicateKey = errors.New("key already defined") // ErrKeyNotFound is returned by Pool.Conn when no connection is registered // for the given key. ErrKeyNotFound = errors.New("no connection for key") )
var Module = fx.Options( fx.Provide(NewPool), fx.Invoke(func(p *Pool, lc fx.Lifecycle) { lc.Append(fx.StopHook(p.Close)) }), )
Module provides a *Pool and binds its lifecycle to the application, closing every pooled connection on shutdown via an fx stop hook.
The pool is deliberately not opened as a whole on start. It also holds the router's loopback connections to sockets this application binds itself, which do not exist until the proxy start hooks run; waiting on those here would block on work a later hook has yet to do. Opening eager connections is the job of whoever owns one, through WaitReady.
Functions ¶
func WaitReady ¶ added in v0.3.0
WaitReady opens conns and blocks until each is ready or ctx is done, whichever comes first. They are waited on concurrently, so they share ctx's deadline rather than consuming it in turn, and every target that never came up is reported rather than only the first, so one unreachable address cannot mask another.
When ctx has a deadline this stops just short of it. Callers are fx start hooks, and fx prefers its start context's error over what a hook returns (app.go, withTimeout), so a wait that runs to the deadline is reported as a bare "context deadline exceeded" and the target names are lost. Returning early is what keeps them.
Types ¶
type Conn ¶
type Conn struct {
// contains filtered or unexported fields
}
Conn is a grpc.ClientConnInterface that resolves its dial target per call through a Resolver and fetches (lazily creating) the underlying pooled connection through a ConnFactory. With a dynamic Resolver a single Conn fronts many physical connections (e.g. one per namespace); with a static Resolver it always resolves to the same one. Construct one with NewConn.
func NewConn ¶
func NewConn(f ConnFactory, r Resolver) (*Conn, error)
NewConn returns a Conn that resolves through r and dials through f. When r is static the pooled connection is created eagerly here, so a malformed target or bad dial option surfaces at construction rather than on the first request. Creating it is not the same as opening it: gRPC connects on demand, so no socket exists until Conn.WaitReady or the first request. A dynamic resolver defers creation to the first call that resolves a given target.
func (*Conn) Invoke ¶
func (c *Conn) Invoke(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error
Invoke resolves the connection for this call and forwards the unary RPC to it, satisfying grpc.ClientConnInterface.
func (*Conn) NewStream ¶
func (c *Conn) NewStream( ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption, ) (grpc.ClientStream, error)
NewStream resolves the connection for this call and opens the stream on it, satisfying grpc.ClientConnInterface. The target is resolved from ctx before any message is sent, so streaming and unary calls share one resolution path.
func (*Conn) WaitReady ¶ added in v0.3.0
WaitReady opens the underlying connection and blocks until it is ready or ctx is done, whichever comes first. NewConn creates a static Conn's connection but grpc.NewClient only dials on demand, so nothing is open until this runs (or the first request arrives); this is what makes the connection real ahead of serving traffic.
A refused connection is not on its own fatal: gRPC retries with backoff, so a target still coming up passes as long as it answers before ctx expires. gRPC keeps the underlying dial error private, so one that never answers is reported by the state it was stuck in, wrapping ctx's error.
A dynamic Conn holds no connection until a request resolves one, so there is nothing to open and this does nothing. Callers can pass a mixed set of conns without sorting them first.
type ConnFactory ¶
type ConnFactory func(string, string, ...grpc.DialOption) (*grpc.ClientConn, error)
ConnFactory returns the pooled connection for a (key, target) pair, creating it on first use. Pool.ConnOrCreate satisfies this signature: the first argument is the logical cache key and the second is the dial address.
type Pool ¶
type Pool struct {
// contains filtered or unexported fields
}
Pool is a concurrency-safe set of gRPC client connections keyed by a caller-supplied logical key, which is distinct from the dial target. Callers that need two connections to the same dial target (e.g. the same host:port with different TLS server names) must use different keys, or they will collapse onto whichever connection was dialed first. The zero value is not usable; create one with NewPool.
func (*Pool) Close ¶
Close shuts down every connection in the pool, joining any errors. It runs at most once; subsequent calls return the same result without re-closing.
func (*Pool) Conn ¶
func (p *Pool) Conn(key string) (*grpc.ClientConn, error)
Conn returns the connection registered for key. It returns ErrKeyNotFound if no connection is registered for that key.
func (*Pool) ConnOrCreate ¶
func (p *Pool) ConnOrCreate(key, target string, opts ...grpc.DialOption) (*grpc.ClientConn, error)
ConnOrCreate returns the connection registered for key, creating and registering one with grpc.NewClient(target, opts...) when none exists yet. key is the logical cache key and target is the dial address; callers that need distinct connections to the same target (e.g. identical host:port with different TLS server names) must pass distinct keys. If callers race to create the same key, each constructs a client but only one connection is kept; the losers are closed and every caller receives the same *grpc.ClientConn.
type Resolver ¶
type Resolver interface {
IsStatic() bool
Resolve(context.Context) (string, string, []grpc.DialOption, error)
}
Resolver decides, per request, which connection a Conn should use. Resolve returns the pool cache key, the dial target, and the dial options for that connection. IsStatic reports whether the resolution is fixed for the life of the Conn: a static resolver has its connection created when the Conn is, and opened up front by Conn.WaitReady; a dynamic one is resolved lazily on every call.
func StaticResolver ¶
func StaticResolver(hostPort string, opts ...grpc.DialOption) Resolver
StaticResolver returns a Resolver that always resolves to hostPort with the given dial options. It reports IsStatic as true, so a Conn built from it is created eagerly and reuses a single pooled connection keyed by hostPort.