kubernetes apiserver源码: PollImmediateUntil

397 阅读3分钟

这个函数经常用于轮询客户端缓存是否初始化成功。

周期性检查

  • 周期性检查:time.NewTicker会周期性会往tick.C中输入一个值
  • 超时设置:timer := time.NewTimer(timeout) 设置了超时时间,从运行这个函数开始算起的时间间隔。
// poller returns a WaitFunc that will send to the channel every interval until
// timeout has elapsed and then closes the channel.
//
// Over very short intervals you may receive no ticks before the channel is
// closed. A timeout of 0 is interpreted as an infinity, and in such a case
// it would be the caller's responsibility to close the done channel.
// Failure to do so would result in a leaked goroutine.
//
// Output ticks are not buffered. If the channel is not ready to receive an
// item, the tick is skipped.
func poller(interval, timeout time.Duration) WaitFunc {
	return WaitFunc(func(done <-chan struct{}) <-chan struct{} {
		ch := make(chan struct{})

		go func() {
			defer close(ch)

			tick := time.NewTicker(interval)
			defer tick.Stop()

			var after <-chan time.Time
			if timeout != 0 {
				// time.After is more convenient, but it
				// potentially leaves timers around much longer
				// than necessary if we exit early.
				timer := time.NewTimer(timeout)
				after = timer.C
				defer timer.Stop()
			}

			for {
				select {
				case <-tick.C:
					// If the consumer isn't ready for this signal drop it and
					// check the other channels.
					select {
					case ch <- struct{}{}:
					default:
					}
				case <-after:
					return
				case <-done:
					return
				}
			}
		}()

		return ch
	})
}

Context的用法

这里是一个很经典的context.WithCancel的使用场景, 传入的ParentCh如果停止了, 则调用Context的cancel函数,其他go routine就会通过ctx.Done()感知到退出信号。

// contextForChannel derives a child context from a parent channel.
//
// The derived context's Done channel is closed when the returned cancel function
// is called or when the parent channel is closed, whichever happens first.
//
// Note the caller must *always* call the CancelFunc, otherwise resources may be leaked.
func contextForChannel(parentCh <-chan struct{}) (context.Context, context.CancelFunc) {
	ctx, cancel := context.WithCancel(context.Background())

	go func() {
		select {
		case <-parentCh:
			cancel()
		case <-ctx.Done():
		}
	}()
	return ctx, cancel
}

为了演示这个退出信号怎么传入,下面Code部分的main函数可改成:

这个样例会:

  1. 接受系统的Ctrl+C信号,然后传递给stopCh
  2. PollImmediateUntil退出轮询,然后传递给stopGrace
  3. stopGracefor select语句捕捉到退出主流程。

Unbuffered Channel写入会阻塞,等待其他go routine读取; 而 Buffered Channel 到达容量前都不会阻塞;

func main() {
        // Buffered Channel Group
	stop:=make(chan os.Signal,1) 
	stopCh:=make(chan struct{},1)
        // 优雅停机
	stopGrace:=make(chan struct{},1)

	defer fmt.Println("stopped,close signal")
	defer close(stop)
	defer close(stopCh)
	defer close(stopGrace)
	// catch up ctrl + c interrupt
	signal.Notify(stop,os.Interrupt)
	go func() {
                
		err:=PollImmediateUntil(3*time.Second, func() (done bool, err error) {
			fmt.Println("[INFO] doing something time consuming")
			if rand.Intn(10) % 3 == 0{
				// mimic do something time consuming
				fmt.Println("success sync the cache")
				time.Sleep(1*time.Second)
				return true,nil
			}

			return false, nil
		},stopCh)
		if err != nil {
			fmt.Print(err.Error())
		}
		stopGrace<- struct{}{}

	}()


	for {
		select {
		case s:= <-stop:
			fmt.Println("got signal:",s)
			stopCh<- struct{}{}
		case <-stopGrace:
			fmt.Println("stop gracefully")
			return
		default:
		}
	}


}

Code

下面这个是将其从源码拿出来,可作为工具函数使用。

package main

import (
	"context"
	"errors"
	"fmt"
	"math/rand"
	"time"
)



func main() {
	stopCh:=make(chan struct{})
	err:=PollImmediateUntil(3*time.Second, func() (done bool, err error) {
		fmt.Println("[INFO] doing something time consuming")
		if rand.Intn(10) % 3 == 0{
			// mimic do something time consuming
			time.Sleep(1*time.Second)
			return true,nil
		}

		return false, nil
	},stopCh)

	if err != nil {
		fmt.Print(err.Error())
	}

}

// ConditionFunc returns true if the condition is satisfied, or an error
// if the loop should be aborted.
type ConditionFunc func() (done bool, err error)

// WaitFunc creates a channel that receives an item every time a test
// should be executed and is closed when the last test should be invoked.
type WaitFunc func(done <-chan struct{}) <-chan struct{}



// ErrWaitTimeout is returned when the condition exited without success.
var ErrWaitTimeout = errors.New("timed out waiting for the condition")



// PollImmediateUntil tries a condition func until it returns true, an error or stopCh is closed.
//
// PollImmediateUntil runs the 'condition' before waiting for the interval.
// 'condition' will always be invoked at least once.
func PollImmediateUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error {
        // 先试一次,如果不行,开始周期性轮询
	done, err := condition()
	if err != nil {
		return err
	}
	if done {
		return nil
	}
	select {
	case <-stopCh:
		return ErrWaitTimeout
	default:
		return PollUntil(interval, condition, stopCh)
	}
}
// PollUntil tries a condition func until it returns true, an error or stopCh is
// closed.
//
// PollUntil always waits interval before the first run of 'condition'.
// 'condition' will always be invoked at least once.
func PollUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error {
	ctx, cancel := contextForChannel(stopCh)
	defer cancel()
	return WaitFor(poller(interval, 0), condition, ctx.Done())
}
// contextForChannel derives a child context from a parent channel.
//
// The derived context's Done channel is closed when the returned cancel function
// is called or when the parent channel is closed, whichever happens first.
//
// Note the caller must *always* call the CancelFunc, otherwise resources may be leaked.
func contextForChannel(parentCh <-chan struct{}) (context.Context, context.CancelFunc) {
	ctx, cancel := context.WithCancel(context.Background())

	go func() {
		select {
		case <-parentCh:
			cancel()
		case <-ctx.Done():
		}
	}()
	return ctx, cancel
}

// WaitFor continually checks 'fn' as driven by 'wait'.
//
// WaitFor gets a channel from 'wait()'', and then invokes 'fn' once for every value
// placed on the channel and once more when the channel is closed. If the channel is closed
// and 'fn' returns false without error, WaitFor returns ErrWaitTimeout.
//
// If 'fn' returns an error the loop ends and that error is returned. If
// 'fn' returns true the loop ends and nil is returned.
//
// ErrWaitTimeout will be returned if the 'done' channel is closed without fn ever
// returning true.
//
// When the done channel is closed, because the golang `select` statement is
// "uniform pseudo-random", the `fn` might still run one or multiple time,
// though eventually `WaitFor` will return.
func WaitFor(wait WaitFunc, fn ConditionFunc, done <-chan struct{}) error {
	stopCh := make(chan struct{})
	defer close(stopCh)
	c := wait(stopCh)
	for {
		select {
		case _, open := <-c:
			ok, err := fn()
			if err != nil {
				return err
			}
			if ok {
				return nil
			}
			if !open {
				return ErrWaitTimeout
			}
		case <-done:
			return ErrWaitTimeout
		}
	}
}

// poller returns a WaitFunc that will send to the channel every interval until
// timeout has elapsed and then closes the channel.
//
// Over very short intervals you may receive no ticks before the channel is
// closed. A timeout of 0 is interpreted as an infinity, and in such a case
// it would be the caller's responsibility to close the done channel.
// Failure to do so would result in a leaked goroutine.
//
// Output ticks are not buffered. If the channel is not ready to receive an
// item, the tick is skipped.
func poller(interval, timeout time.Duration) WaitFunc {
	return WaitFunc(func(done <-chan struct{}) <-chan struct{} {
		ch := make(chan struct{})

		go func() {
			defer close(ch)

			tick := time.NewTicker(interval)
			defer tick.Stop()

			var after <-chan time.Time
			if timeout != 0 {
				// time.After is more convenient, but it
				// potentially leaves timers around much longer
				// than necessary if we exit early.
				timer := time.NewTimer(timeout)
				after = timer.C
				defer timer.Stop()
			}

			for {
				select {
				case <-tick.C:
					// If the consumer isn't ready for this signal drop it and
					// check the other channels.
					select {
					case ch <- struct{}{}:
					default:
					}
				case <-after:
					return
				case <-done:
					return
				}
			}
		}()

		return ch
	})
}