遇到一个golang time.Tick的坑

time.Tick的巨坑

go版本

>go version
go version go1.15.4 windows/amd64

api文档

func Tick(d Duration) <-chan Time
    Tick is a convenience wrapper for NewTicker providing access to the ticking channel only. 
    While Tick is useful for clients that have no need to shut down the Ticker, be aware that 
    without a way to shut it down the underlying Ticker cannot be recovered by the garbage 
    collector; it "leaks". Unlike NewTicker, Tick will return nil if d <= 0.

问题源码

  • 现象: etcd关闭expired后循环不再进入
	for range time.Tick(time.Duration(ticker) * time.Second) {
		now := time.Now().Unix()
		lvs := make(map[string]*LeaseResult)
		leaseIndex.Range(func(key, value interface{}) bool {
			if v := value.(*LeaseResult); v.Exp <= now {
				lvs[key.(string)] = v
			}
			return true
		})
		var err error
		for lk, lv := range lvs {
			if err = client.SendKeepAlive(lv.Id); err != nil {
				if LeaseTimeoutError(err) {
					// 如果stream关闭(etcd连接断开)重连后相关的lease可能过期,届时会报错
					if DEBUG {
						base.DefaultLogger.Debugf("etcd lease reset (send): lid=%x, key=%v", lv.Id, lk)
					}
					if id, ttl, err := client.Grant(expire); err == nil {
						if err = client.Put(lk, lv.Val, id); err == nil {
							lv.Id = id
							lv.Exp = Exp(now, ttl)
							leaseIndex.Store(lk, lv)
						}
					}
				} else {
					return err
				}
			}
			if DEBUG {
				base.DefaultLogger.Debugf("etcd lease send: lid=%x, key=%v, err=%v", lv.Id, lk, err)
			}
		}
	}

解决源码

    // FIXBUG: 必须注意, 此处不能使用"for range ticker"的写法,在etcd关闭expire+后循环不再执行! 相当奇怪!
	for {
		now := time.Now().Unix()
		lvs := make(map[string]*LeaseResult)
		leaseIndex.Range(func(key, value interface{}) bool {
			if v := value.(*LeaseResult); v.Exp <= now {
				lvs[key.(string)] = v
			}
			return true
		})
		var err error
		for lk, lv := range lvs {
			if err = client.SendKeepAlive(lv.Id); err != nil {
				if LeaseTimeoutError(err) {
					// 如果stream关闭(etcd连接断开)重连后相关的lease可能过期,届时会报错
					if DEBUG {
						base.DefaultLogger.Debugf("etcd lease reset (send): lid=%x, key=%v", lv.Id, lk)
					}
					if id, ttl, err := client.Grant(expire); err == nil {
						if err = client.Put(lk, lv.Val, id); err == nil {
							lv.Id = id
							lv.Exp = Exp(now, ttl)
							leaseIndex.Store(lk, lv)
						}
					}
				} else {
					return err
				}
			}
			if DEBUG {
				base.DefaultLogger.Debugf("etcd lease send: lid=%x, key=%v, err=%v", lv.Id, lk, err)
			}
		}
		time.Sleep(time.Duration(ticker) * time.Second)
	}

原因归结

1. time.Tick()生成某个channel
2. 内部往channel push
3. 循环从channel pop
4. 一旦内部失败,则循环永久阻塞...
posted @ 2021-02-24 10:43  zolo®  阅读(266)  评论(0编辑  收藏  举报