Copilot commented on code in PR #3438:
URL: https://github.com/apache/dubbo-go/pull/3438#discussion_r3426254627


##########
remoting/exchange_client.go:
##########
@@ -82,19 +82,28 @@ func NewExchangeClient(url *common.URL, client Client, 
connectTimeout time.Durat
 }
 
 func (cl *ExchangeClient) doInit(url *common.URL) error {
-       if cl.init {
+       if cl.init.Load() {
                return nil
        }
+       // Use CompareAndSwap to ensure only one goroutine performs 
initialization.
+       // If CAS fails, another goroutine already initialized; spin-wait until 
init completes.
+       if !cl.init.CAS(false, true) {
+               // Another goroutine is initializing; wait for it to complete.
+               for !cl.init.Load() {
+                       time.Sleep(10 * time.Millisecond)
+               }
+               return nil
+       }
+       // This goroutine won the CAS — perform the actual initialization.
        if cl.client.Connect(url) != nil {
                // retry for a while
                time.Sleep(100 * time.Millisecond)
                if cl.client.Connect(url) != nil {
                        logger.Errorf("[Remoting] failed to connect server, 
url=%v", url.Location)
+                       cl.init.Store(false) // reset on failure so future 
calls can retry
                        return errors.New("Failed to connect server " + 
url.Location)
                }
        }
-       // FIXME atomic operation
-       cl.init = true
        return nil
 }

Review Comment:
   The CAS sets `init` to true *before* `Connect` succeeds, so concurrent 
callers may observe `init==true` and return nil while initialization is still 
in progress (or even if it later fails and resets). This can lead to callers 
proceeding with an unconnected client and can also cause errors from the 
initializing goroutine to be lost to concurrent callers. Use a separate 
initialization state (e.g., 3-state atomic: uninitialized / initializing / 
initialized) plus a wait mechanism, or replace this with `sync.Once` + an error 
channel/mutex to ensure (1) other goroutines wait for completion and (2) they 
see the same error result.



##########
remoting/exchange_client.go:
##########
@@ -82,19 +82,28 @@ func NewExchangeClient(url *common.URL, client Client, 
connectTimeout time.Durat
 }
 
 func (cl *ExchangeClient) doInit(url *common.URL) error {
-       if cl.init {
+       if cl.init.Load() {
                return nil
        }
+       // Use CompareAndSwap to ensure only one goroutine performs 
initialization.
+       // If CAS fails, another goroutine already initialized; spin-wait until 
init completes.
+       if !cl.init.CAS(false, true) {
+               // Another goroutine is initializing; wait for it to complete.
+               for !cl.init.Load() {
+                       time.Sleep(10 * time.Millisecond)
+               }

Review Comment:
   This introduces a fixed-interval sleep-based spin loop, which adds avoidable 
latency and can be wasteful under contention. If you keep an atomic-state 
approach, prefer a proper coordination primitive (e.g., a `sync.Cond`, channel 
signaling completion, or `sync.Once` + blocking wait) so waiters sleep until 
notified rather than polling.



##########
remoting/exchange_client_test.go:
##########
@@ -104,3 +104,47 @@ func TestExchangeClientIsAvailable(t *testing.T) {
        m.mu.Unlock()
        assert.False(t, ec.IsAvailable())
 }
+
+func TestExchangeClientConcurrentDoInit(t *testing.T) {
+       // Verify that concurrent calls to doInit only result in a single 
Connect call.
+       m := &mockClient{available: true}
+       ec := NewExchangeClient(testURL(), m, 5*time.Second, true)
+
+       var wg sync.WaitGroup
+       const goroutines = 20
+       wg.Add(goroutines)
+       for i := 0; i < goroutines; i++ {
+               go func() {
+                       defer wg.Done()
+                       // All goroutines trigger lazy init concurrently via 
Request-like paths
+                       if err := ec.doInit(testURL()); err != nil {
+                               t.Errorf("doInit failed: %v", err)
+                       }
+               }()
+       }

Review Comment:
   Calling `t.Errorf` from multiple goroutines can be problematic and is 
commonly flagged by race/lint tooling. Prefer collecting errors in a buffered 
channel (or `atomic`/mutex-protected slice) and asserting/reporting from the 
main test goroutine after `wg.Wait()` to avoid potential data races and to 
ensure clean test output.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to