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


##########
registry/base_registry.go:
##########
@@ -340,23 +341,44 @@ func (r *BaseRegistry) Subscribe(url *common.URL, 
notifyListener NotifyListener)
                                return err
                        }

Review Comment:
   When DoSubscribe returns an error during shutdown, Subscribe currently 
returns that subscribe error (line 341) instead of the stable unavailable 
error. This makes shutdown exit behavior timing-dependent (Destroy between 
DoSubscribe and waitRetryDelay changes the returned error) and contradicts the 
intent of returning the existing unavailable error on destruction.



##########
cluster/cluster/failback/cluster_invoker.go:
##########
@@ -166,15 +199,129 @@ func (invoker *failbackClusterInvoker) Invoke(ctx 
context.Context, invocation pr
        return res
 }
 
+func (invoker *failbackClusterInvoker) isStopped() bool {
+       invoker.lifecycleMu.Lock()
+       defer invoker.lifecycleMu.Unlock()
+       return invoker.stopped
+}
+
 func (invoker *failbackClusterInvoker) Destroy() {
-       invoker.BaseClusterInvoker.Destroy()
+       invoker.destroyOnce.Do(func() {
+               invoker.lifecycleMu.Lock()
+               invoker.stopped = true
+               if invoker.retryCancel != nil {
+                       invoker.retryCancel()
+               }
+               taskList := invoker.taskList
+               processDone := invoker.processDone
+               retryDone := invoker.retryDone
+               if taskList != nil {
+                       _ = taskList.Dispose()
+               }
+               invoker.lifecycleMu.Unlock()
+
+               invoker.waitForShutdown(processDone, retryDone)
+               invoker.BaseClusterInvoker.Destroy()
+       })
+}
 
-       // stop ticker
-       if invoker.ticker != nil {
-               invoker.ticker.Stop()
+func (invoker *failbackClusterInvoker) enqueueInitialRetry(ctx 
context.Context, retryTask *retryTimerTask) {
+       invoker.lifecycleMu.Lock()
+       defer invoker.lifecycleMu.Unlock()
+
+       if invoker.stopped || invoker.Destroyed.Load() {
+               return
+       }
+
+       if invoker.taskList == nil {
+               if ctx == nil {
+                       ctx = context.Background()
+               }
+               invoker.retryCtx, invoker.retryCancel = 
context.WithCancel(context.WithoutCancel(ctx))
+               invoker.taskList = queue.New(invoker.failbackTasks)
+               invoker.processDone = make(chan struct{})
+               go invoker.process(invoker.retryCtx, invoker.taskList, 
invoker.processDone)
+       }
+
+       if invoker.taskList.Len() >= invoker.failbackTasks {
+               logger.Warnf("[Cluster][Failback] task list full, len=%d", 
invoker.taskList.Len())
+               return
        }
 
-       _ = invoker.taskList.Dispose()
+       if err := invoker.taskList.Put(retryTask); err != nil {
+               logger.Warnf("[Cluster][Failback] put initial task failed, 
err=%v", err)
+       }
+}
+
+func (invoker *failbackClusterInvoker) startRetry(ctx context.Context, 
retryTask *retryTimerTask) {
+       invoker.lifecycleMu.Lock()
+       defer invoker.lifecycleMu.Unlock()
+
+       if invoker.stopped || ctx.Err() != nil {
+               return
+       }
+       if invoker.activeRetries == 0 {
+               invoker.retryDone = make(chan struct{})
+       }
+       invoker.activeRetries++
+       retryDone := invoker.retryDone
+       go func() {
+               defer invoker.finishRetry(retryDone)
+               invoker.tryTimerTaskProc(ctx, retryTask)
+       }()
+}
+
+func (invoker *failbackClusterInvoker) finishRetry(retryDone chan struct{}) {
+       invoker.lifecycleMu.Lock()
+       defer invoker.lifecycleMu.Unlock()
+
+       invoker.activeRetries--
+       if invoker.activeRetries == 0 && invoker.retryDone == retryDone {
+               close(retryDone)
+       }
+}
+
+func (invoker *failbackClusterInvoker) enqueueRetry(retryTask *retryTimerTask) 
bool {
+       invoker.lifecycleMu.Lock()
+       defer invoker.lifecycleMu.Unlock()
+
+       if invoker.stopped || invoker.retryCtx == nil || invoker.retryCtx.Err() 
!= nil || invoker.taskList == nil {
+               return false
+       }
+
+       retryTask.lastT = time.Now()
+       if err := invoker.taskList.Put(retryTask); err != nil {
+               logger.Warnf("[Cluster][Failback] put retry task failed, 
err=%v", err)
+               return false
+       }
+       return true
+}
+
+func (invoker *failbackClusterInvoker) waitForShutdown(processDone, retryDone 
<-chan struct{}) {
+       if processDone == nil && retryDone == nil {
+               return
+       }
+
+       timer := time.NewTimer(constant.DefaultShutdownConfigStepTimeout)
+       defer timer.Stop()
+
+       wait := func(done <-chan struct{}, name string) bool {
+               if done == nil {
+                       return true
+               }
+               select {
+               case <-done:
+                       return true
+               case <-timer.C:
+                       logger.Warnf("[Cluster][Failback] timed out waiting for 
%s shutdown", name)
+                       return false
+               }
+       }
+
+       if !wait(processDone, "retry processor") {
+               return
+       }
+       _ = wait(retryDone, "retry tasks")
 }

Review Comment:
   waitForShutdown uses a single shared timer for both waits; if the first wait 
consumes most of DefaultShutdownConfigStepTimeout, the second wait can time out 
almost immediately. Since the constant name implies a per-step timeout, each 
wait should have its own timer budget.



-- 
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