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.
问题源码
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. 一旦内部失败,则循环永久阻塞...