From d73edff71e2603cd44a5cd0955a3d30bebc7814b Mon Sep 17 00:00:00 2001
From: Toby <tobyxdd@gmail.com>
Date: Thu, 28 Dec 2023 15:10:21 -0800
Subject: [PATCH] fix: lazy mode should defer config evaluation

---
 app/cmd/client.go        | 26 ++++++++++++--------------
 core/client/reconnect.go | 25 +++++++++++++------------
 2 files changed, 25 insertions(+), 26 deletions(-)

diff --git a/app/cmd/client.go b/app/cmd/client.go
index 80516af..b20facb 100644
--- a/app/cmd/client.go
+++ b/app/cmd/client.go
@@ -398,21 +398,19 @@ func runClient(cmd *cobra.Command, args []string) {
 	if err := viper.Unmarshal(&config); err != nil {
 		logger.Fatal("failed to parse client config", zap.Error(err))
 	}
-	hyConfig, err := config.Config()
-	if err != nil {
-		logger.Fatal("failed to load client config", zap.Error(err))
-	}
 
-	c, err := client.NewReconnectableClient(hyConfig, func(c client.Client, info *client.HandshakeInfo, count int) {
-		connectLog(info, count)
-		// On the client side, we start checking for updates after we successfully connect
-		// to the server, which, depending on whether lazy mode is enabled, may or may not
-		// be immediately after the client starts. We don't want the update check request
-		// to interfere with the lazy mode option.
-		if count == 1 && !disableUpdateCheck {
-			go runCheckUpdateClient(c)
-		}
-	}, config.Lazy)
+	c, err := client.NewReconnectableClient(
+		config.Config,
+		func(c client.Client, info *client.HandshakeInfo, count int) {
+			connectLog(info, count)
+			// On the client side, we start checking for updates after we successfully connect
+			// to the server, which, depending on whether lazy mode is enabled, may or may not
+			// be immediately after the client starts. We don't want the update check request
+			// to interfere with the lazy mode option.
+			if count == 1 && !disableUpdateCheck {
+				go runCheckUpdateClient(c)
+			}
+		}, config.Lazy)
 	if err != nil {
 		logger.Fatal("failed to initialize client", zap.Error(err))
 	}
diff --git a/core/client/reconnect.go b/core/client/reconnect.go
index 9a94bd3..137285f 100644
--- a/core/client/reconnect.go
+++ b/core/client/reconnect.go
@@ -10,23 +10,21 @@ import (
 // reconnectableClientImpl is a wrapper of Client, which can reconnect when the connection is closed,
 // except when the caller explicitly calls Close() to permanently close this client.
 type reconnectableClientImpl struct {
-	config        *Config
+	configFunc    func() (*Config, error)           // called before connecting
+	connectedFunc func(Client, *HandshakeInfo, int) // called when successfully connected
 	client        Client
 	count         int
-	connectedFunc func(Client, *HandshakeInfo, int) // called when successfully connected
 	m             sync.Mutex
 	closed        bool // permanent close
 }
 
-func NewReconnectableClient(config *Config, connectedFunc func(Client, *HandshakeInfo, int), lazy bool) (Client, error) {
-	// Make sure we capture any error in config and return it here,
-	// so that the caller doesn't have to wait until the first call
-	// to TCP() or UDP() to get the error (when lazy is true).
-	if err := config.verifyAndFill(); err != nil {
-		return nil, err
-	}
+// NewReconnectableClient creates a reconnectable client.
+// If lazy is true, the client will not connect until the first call to TCP() or UDP().
+// We use a function for config mainly to delay config evaluation
+// (which involves DNS resolution) until the actual connection attempt.
+func NewReconnectableClient(configFunc func() (*Config, error), connectedFunc func(Client, *HandshakeInfo, int), lazy bool) (Client, error) {
 	rc := &reconnectableClientImpl{
-		config:        config,
+		configFunc:    configFunc,
 		connectedFunc: connectedFunc,
 	}
 	if !lazy {
@@ -41,9 +39,12 @@ func (rc *reconnectableClientImpl) reconnect() error {
 	if rc.client != nil {
 		_ = rc.client.Close()
 	}
-	var err error
 	var info *HandshakeInfo
-	rc.client, info, err = NewClient(rc.config)
+	config, err := rc.configFunc()
+	if err != nil {
+		return err
+	}
+	rc.client, info, err = NewClient(config)
 	if err != nil {
 		return err
 	} else {