nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
// parent 不是 cancelCtx 类型,可能是用户自己实现的Context
atomic.AddInt32(&goroutines, +1)
// 启动一个协程监听,如果 parent 取消了,子 Context 也取消
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
cancel 方法就是来取消 cancelCtx,主要的工作是:关闭c.done 中的channel,给 err 赋值,然后级联取消所有 子Context。如果 removeFromParent 为 true,会从父节点中删除以该节点为树顶的树。
cancel() 方法只负责自己管辖的范围,即自己以及自己的子节点,然后根据配置判断是否需要从父节点中移除自己为顶点的树。如果子节点还有子节点,那么由子节点负责处理,不用自己负责了。
propagateCancel() 中有三处调用了 cancel() 方法,传入的 removeFromParent 都为 false,是因为当时根本没有挂载,不需要移除。而 WithCancel 返回的 CancelFunc ,传入的 removeFromParent 为 true,是因为调用 propagateCancel 有可能产生挂载,当产生挂载时,调用 cancel() 就需要移除了。
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
// err 是指取消的原因,必传,cancelCtx 中是 errors.New("context canceled")
if err == nil {
panic("context: internal error: missing cancel error")
}
// 涉及到保护字段值的修改,都需要加锁
c.mu.Lock()
// 如果该Context已经取消过了,直接返回。多次调用cancel,不会产生额外效果
if c.err != nil {
c.mu.Unlock()
return
}
// 给 err 赋值,这里 err 一定不为 nil
c.err = err
// close channel
d, _ := c.done.Load().(chan struct{})
// 因为c.done 是懒加载,有可能存在 nil 的情况
// 如果 c.done 中没有值,直接赋值 closedchan;否则直接 close
if d == nil {
c.done.Store(closedchan)
} else {
close(d)
}
// 遍历当前 cancelCtx 所有的子Context,让子节点也 cancel
// 因为当前的Context 会主动把子Context移除,子Context 不用主动从parent中脱离
// 因此 child.cancel 传入的 removeFromParent 为false
for child := range c.children {
child.cancel(false, err)
}
// 将 children 置空,相当于移除自己的所有子Context
c.children = nil
c.mu.Unlock()
// 如果当前 cancelCtx 需要从上层的 cancelCtx移除,调用removeChild方法
// c.Context 就是自己的父Context
if removeFromParent {
removeChild(c.Context, c)
}
}
从propagateCancel方法中可以看到,只有parent 属于 cancelCtx 类型 ,才会将自己挂载。因此 removeChild 会再次判断 parent 是否为 cancelCtx,和之前的逻辑保持一致。找到的话,再将自己移除,需要注意的是,移除会把自己及其自己下面的所有子节点都移除。
如果上一步 propagateCancel 方法将自己挂载到了 A 上,但是在调用 cancel() 时,A 已经取消过了,此时 parentCancelCtx() 会返回 false。不过这没有关系,A 取消时已经将挂载的子节点移除了,当前的子节点不用将自己从 A 中移除了。
func removeChild(parent Context, child canceler) {
// parent 是否为未取消的 cancelCtx
p, ok := parentCancelCtx(parent)
if !ok {
return
}
// 获取 parent cancelCtx 的锁,修改保护字段 children
p.mu.Lock()
// 将自己从 parent cancelCtx 的 children 中删除
if p.children != nil {
delete(p.children, child)
}
p.mu.Unlock()
}
parentCancelCtx 判断 parent 是否为 未取消的 *cancelCtx。取消与否容易判断,难判断的是 parent 是否为 *cancelCtx,因为有可能其他结构体内嵌了 cancelCtx,比如 timerCtx,会通过比对 channel 来确定。
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
// 如果 parent context 的 done 为 nil, 说明不支持 cancel,那么就不可能是 cancelCtx
// 如果 parent context 的 done 为 closedchan, 说明 parent context 已经 cancel 了
done := parent.Done()
if done == closedchan || done == nil {
return nil, false
}
// 到这里说明支持取消,且没有被取消
// 如果 parent context 属于原生的 *cancelCtx 或衍生类型,需要继续进行后续判断
// 如果 parent context 无法转换到 *cancelCtx,则认为非 cancelCtx,返回 nil,fasle
p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
if !ok {
return nil, false
}
// 经过上面的判断后,说明 parent context 可以被转换为 *cancelCtx,这时存在多种情况:
// - parent context 就是 *cancelCtx
// - parent context 是标准库中的 timerCtx
// - parent context 是个自己自定义包装的 cancelCtx
//
// 针对这 3 种情况需要进行判断,判断方法就是:
// 判断 parent context 通过 Done