// BackoffUntil loops until stop channel is closed, run f every duration given by BackoffManager.
//
// If sliding is true, the period is computed after f runs. If it is false then
// period includes the runtime for f.
funcBackoffUntil(ffunc(),backoffBackoffManager,slidingbool,stopCh<-chanstruct{}){vartclock.Timer//先定義一個空的 timer 等等用來接 backoffmanger 算出的 timer
for{//無限迴圈開始
select{case<-stopCh://當收到 stop channel 時關閉整個 BackoffUntil function 的生命週期
returndefault://防止 channel卡住
}//如果 sliding 不開啟的話,會先從 BackoffManger 算出 timer
//仔細看下面的話會發現 timer 的時間會包含執行使用者所指定的 function 後才等待 timer 的 ticker
if!sliding{t=backoff.Backoff()}//這裡的做法就是卻跑每次執行使用者所指定的 Function 後都會跑入 defer確保panic 處理
func(){deferruntime.HandleCrash()f()}()//那一個if !sliding 有點類似 ,差別在於這裡是執行完使用者所指定的 function 後才算出 timer , timer 等待時間不包含執行使用者的 function
ifsliding{t=backoff.Backoff()}//在 golang select channel 有個小問題
//當 select A channel , B channel , C channel 時
//A B C channel 都剛剛好都有訊號同時到達那 select channel 會選哪一個呢?
//在 golang 的世界中答案是隨機的, A B C channel 哪一個都有可能被選到xD
//當然 kubernetes 的開發者們一定也知道這個問題,在這裡就有了相應的註解
//我這裡就保留原始的註解,整段註解的大意大概是如果 stop channel 與 ticker channel 同時到達
//因為golnag select chennel 機制剛好選中 ticker channel 那會造成使用者指定的 function 多跑一次,這樣是不符合預期的行為。
//因此在for loop 的一開始會判斷 stop channel 是否有訊號
//用來防止 stop channel 與 ticker channel 同時到達並且golang select channel 剛好選中 ticker 的問題
// NOTE: b/c there is no priority selection in golang
// it is possible for this to race, meaning we could
// trigger t.C and stopCh, and t.C select falls through.
// In order to mitigate we re-check stopCh at the beginning
// of every loop to prevent extra executions of f().
select{case<-stopCh:returncase<-t.C():}}}
example
範例簡單的帶一下怎麼使用這個 function ,我們把重點看在 wait.BackoffUntil 就好其他就先不要管,用法十分簡單直接來看 code。
funcstartDBSizeMonitorPerEndpoint(client*clientv3.Client,intervaltime.Duration)(func(),error){...ctx,cancel:=context.WithCancel(context.Background())for_,ep:=rangeclient.Endpoints(){if_,found:=dbMetricsMonitors[ep];found{continue}dbMetricsMonitors[ep]=struct{}{}endpoint:=epklog.V(4).Infof("Start monitoring storage db size metric for endpoint %s with polling interval %v",endpoint,interval)gowait.JitterUntilWithContext(ctx,func(context.Context){epStatus,err:=client.Maintenance.Status(ctx,endpoint)iferr!=nil{klog.V(4).Infof("Failed to get storage db size for ep %s: %v",endpoint,err)metrics.UpdateEtcdDbSize(endpoint,-1)}else{metrics.UpdateEtcdDbSize(endpoint,epStatus.DbSize)}},interval,dbMetricsMonitorJitter,true)}returnfunc(){cancel()},nil}
UntilWithContext
這就表示定時會呼叫使用者指定的 function ,若是外面有人將 context cancel 掉,那就會結束 wait.UntilWithContext 的生命週期。基本上複用了 JitterUntilWithContext 差別在每此重新呼叫 function 的間隔時間有沒有抖動而已。
funcTestUntilWithContext(t*testing.T){//建立 with cancel 的 context
ctx,cancel:=context.WithCancel(context.TODO())//直接種 context 的生命週期
cancel()//這時候在透過 UntilWithContext 呼叫我們自定義的 function
//記得....因為 context 生命週期已經死掉了照理來說不會觸發我們自訂的 function 。
UntilWithContext(ctx,func(context.Context){t.Fatal("should not have been invoked")},0)//建立 with cancel 的 context
ctx,cancel=context.WithCancel(context.TODO())//建立取消的 channel
called:=make(chanstruct{})//啟動一個 go routine ,主要透過異步的經由 UntilWithContext 定時呼叫自定義的 function
//因為 main thread 會卡在 <-called,當執行到 goroutine 時會送訊號到 called channel,讓外部收到
gofunc(){UntilWithContext(ctx,func(context.Context){called<-struct{}{}},0)//發送完訊號後就關閉 channel
close(called)}()//收到 goroutine才會往下繼續執行
<-called//關閉 context 的生命週期讓 UntilWithContext 不再執行我們自定義的 function
cancel()//收到關閉 channel 的訊號
<-called}