@@ -4,19 +4,25 @@ import (
44 "context"
55 "crypto/tls"
66 "crypto/x509"
7+ "errors"
8+ "fmt"
79 "runtime"
810 "time"
911
1012 "github.com/cenkalti/backoff/v4"
1113 log "github.com/sirupsen/logrus"
1214 "google.golang.org/grpc"
15+ "google.golang.org/grpc/connectivity"
1316 "google.golang.org/grpc/credentials"
1417 "google.golang.org/grpc/credentials/insecure"
1518 "google.golang.org/grpc/keepalive"
1619
1720 "github.com/netbirdio/netbird/util/embeddedroots"
1821)
1922
23+ // ErrConnectionShutdown indicates that the connection entered shutdown state before becoming ready
24+ var ErrConnectionShutdown = errors .New ("connection shutdown before ready" )
25+
2026// Backoff returns a backoff configuration for gRPC calls
2127func Backoff (ctx context.Context ) backoff.BackOff {
2228 b := backoff .NewExponentialBackOff ()
@@ -25,6 +31,26 @@ func Backoff(ctx context.Context) backoff.BackOff {
2531 return backoff .WithContext (b , ctx )
2632}
2733
34+ // waitForConnectionReady blocks until the connection becomes ready or fails.
35+ // Returns an error if the connection times out, is cancelled, or enters shutdown state.
36+ func waitForConnectionReady (ctx context.Context , conn * grpc.ClientConn ) error {
37+ conn .Connect ()
38+
39+ state := conn .GetState ()
40+ for state != connectivity .Ready && state != connectivity .Shutdown {
41+ if ! conn .WaitForStateChange (ctx , state ) {
42+ return fmt .Errorf ("wait state change from %s: %w" , state , ctx .Err ())
43+ }
44+ state = conn .GetState ()
45+ }
46+
47+ if state == connectivity .Shutdown {
48+ return ErrConnectionShutdown
49+ }
50+
51+ return nil
52+ }
53+
2854// CreateConnection creates a gRPC client connection with the appropriate transport options.
2955// The component parameter specifies the WebSocket proxy component path (e.g., "/management", "/signal").
3056func CreateConnection (ctx context.Context , addr string , tlsEnabled bool , component string ) (* grpc.ClientConn , error ) {
@@ -42,22 +68,24 @@ func CreateConnection(ctx context.Context, addr string, tlsEnabled bool, compone
4268 }))
4369 }
4470
45- connCtx , cancel := context .WithTimeout (ctx , 30 * time .Second )
46- defer cancel ()
47-
48- conn , err := grpc .DialContext (
49- connCtx ,
71+ conn , err := grpc .NewClient (
5072 addr ,
5173 transportOption ,
5274 WithCustomDialer (tlsEnabled , component ),
53- grpc .WithBlock (),
5475 grpc .WithKeepaliveParams (keepalive.ClientParameters {
5576 Time : 30 * time .Second ,
5677 Timeout : 10 * time .Second ,
5778 }),
5879 )
5980 if err != nil {
60- log .Printf ("DialContext error: %v" , err )
81+ return nil , fmt .Errorf ("new client: %w" , err )
82+ }
83+
84+ ctx , cancel := context .WithTimeout (ctx , 30 * time .Second )
85+ defer cancel ()
86+
87+ if err := waitForConnectionReady (ctx , conn ); err != nil {
88+ _ = conn .Close ()
6189 return nil , err
6290 }
6391
0 commit comments