Client-go中的watch接口的resultChan会自动关闭
问题描述
在client-go工程中,有时候需要用到watch接口,实际场景如下:
namespacesWatch, err := clientSet.CoreV1().Namespaces().Watch(metav1.ListOptions{
})
if err != nil {
klog.Errorf("create watch error, error is %s, program exit!", err.Error())
panic(err)
}
for {
select e, ok := <-namespacesWatch.ResultChan()
if e.Object == nil {
// 这个时候一般是chan已经被关闭了,可以顺便开看下ok是不是flase。
} else {
// 正常处理逻辑
}
}
watch的resultChan会周期性的关闭,我不知道这个周期是不是可以设置,但是我在github的issue中看到,有人5分钟就自动关闭了,我的集群是大概40分钟左右,进一步的追究需要深入,我这边就说我遇到的问题以及解决方法吧。问题就是resultChan会定期自动关闭。github上client-go项目对于该问题的Issues: https://github.com/kubernetes/client-go/issues/623, 上面有大佬回复:No, the server will close watch connections regularly. Re-establishing a watch at the last-received resourceVersion is a normal part of maintaining a watch as a client. There are helpers to do this for you in https://github.com/kubernetes/client-go/tree/master/tools/watch。是有官方解决方法的去client-go的tools目录下的watch文件夹下看下。
resultChan会自动关闭的原因
看一下watch.go中的部分代码:
// Interface can be implemented by anything that knows how to watch and report changes.
type Interface interface {
// Stops watching. Will close the channel returned by ResultChan(). Releases
// any resources used by the watch.
Stop()
// Returns a chan which will receive all the events. If an error occurs
// or Stop() is called, this channel will be closed, in which case the
// watch should be completely cleaned up. !!!明确说了在出现错误或者被调用Stop时,通道会自动关闭的
ResultChan() <-chan Event
}
我们接着看下有哪些error和哪些情况会调用stop,我就开始追watch方法的那个对象,最后追到这里streamwatcher.go,Ok,这里就可以看到watch实际对象了,struct的中参数可以看下,有result的通道,互斥锁和Stoppe标志接收是否已经结束了的标志位。看下NewStreamWatcher方法,关注其中的go sw.receive(),在返回对象前就起了协程在接收数据了,那接着去看receive函数,看一下receive那边的注释,我写的中文注释,就是在解码遇到错误是就会选择return,return前看下defer sw.Stop()等清理操作。这样就跟上面对上了!
// StreamWatcher turns any stream for which you can write a Decoder interface
// into a watch.Interface.
type StreamWatcher struct {
sync.Mutex
source Decoder
reporter Reporter
result chan Event
stopped bool
}
// NewStreamWatcher creates a StreamWatcher from the given decoder.
func NewStreamWatcher(d Decoder, r Reporter) *StreamWatcher {
sw := &StreamWatcher{
source: d,
reporter: r,
// It's easy for a consumer to add buffering via an extra
// goroutine/channel, but impossible for them to remove it,
// so nonbuffered is better.
result: make(chan Event),
}
go sw.receive()// !!!看这里,新建完对象就开始接收数据了
return sw
}
// ResultChan implements Interface.
func (sw *StreamWatcher) ResultChan() <-chan Event {
return sw.result
}
// Stop implements Interface.
func (sw *StreamWatcher) Stop() {
// Call Close() exactly once by locking and setting a flag.
sw.Lock()
defer sw.Unlock()
if !sw.stopped {
sw.stopped = true
sw.source.Close()
}
}
// stopping returns true if Stop() was called previously.
func (sw *StreamWatcher) stopping() bool {
sw.Lock()
defer sw.Unlock()
return sw.stopped
}
// receive reads result from the decoder in a loop and sends down the result channel.
func (sw *StreamWatcher) receive() {
defer close(sw.result)
defer sw.Stop()// 注意看这里,这个方法退出前就会调用stop函数
defer utilruntime.HandleCrash()
for {
// for循环,一直接收
action, obj, err := sw.source.Decode()
if err != nil {
//以下是接收到的错,反正有错误就会return
// Ignore expected error.
if sw.stopping() {
return
}
switch err {
case io.EOF:
// watch closed normally
case io.ErrUnexpectedEOF:
klog.V