雑なメモ書き

気楽にいきます

contextについて(1)

  • ある程度はchannelの時にやってるんですが
  • いざcontextつかって良い感じにやろうと思うと
  • 何が正着だっけという感じで迷うので調べ直しました

基本的なところ

Context

  • DeadLine,Done,Err,Valueを要求するinterface
type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

context.Background()

  • 最初にcontextを生成する為に呼ばれるメソッド
ctx := context.Background()
  • これの正体がなんなのかというと
  • contextパッケージにいるbackgroudを返している
func Background() Context {
    return background
}
  • じゃあbackgroudは何かというと
  • emptyCtxがnewされた物が入っている
background = new(emptyCtx)
  • emptyCtxはただのintです(型はemptyCtxなんですが)
  • 要求されたメソッドはすべて実装されていますが全部偽を返します
type emptyCtx int

func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
    return
}

func (*emptyCtx) Done() <-chan struct{} {
    return nil
}

func (*emptyCtx) Err() error {
    return nil
}

func (*emptyCtx) Value(key interface{}) interface{} {
    return nil
}

context.WithCancel()

  • cancelメソッドを返してくれる
  • 要は中のdoneを操作してcancelを行っている
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
    c := newCancelCtx(parent)
    propagateCancel(parent, &c)
    return &c, func() { c.cancel(true, Canceled) }
}
  • newCancelCtxを作成する
  • これは、cancelCtx型を返している
func newCancelCtx(parent Context) cancelCtx {
    return cancelCtx{Context: parent}
}
  • cancelCtx型は以下の定義
  • Deadlineは埋め込みで回避しているはず
type cancelCtx struct {
    Context

    mu       sync.Mutex            // protects following fields
    done     chan struct{}         // created lazily, closed by first cancel call
    children map[canceler]struct{} // set to nil by the first cancel call
    err      error                 // set to non-nil by the first cancel call
}

func (c *cancelCtx) Done() <-chan struct{} {
    c.mu.Lock()
    if c.done == nil {
        c.done = make(chan struct{})
    }
    d := c.done
    c.mu.Unlock()
    return d
}
  • このDoneは偽が返るselectが実行されても終わらない
  • 公式のここのサンプルが参考になる
  • それで、最終的にcancelが呼ばれた際に
  • 内部メソッドのcancelが呼ばれる
  • doneをcloseさせ、childrenにも再帰的にcancelをかける
  • 最後に親からremoveして終わる
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
    if err == nil {
        panic("context: internal error: missing cancel error")
    }
    c.mu.Lock()
    if c.err != nil {
        c.mu.Unlock()
        return // already canceled
    }
    c.err = err
    if c.done == nil {
        c.done = closedchan
    } else {
        close(c.done)
    }
    for child := range c.children {
        // NOTE: acquiring the child's lock while holding parent's lock.
        child.cancel(false, err)
    }
    c.children = nil
    c.mu.Unlock()

    if removeFromParent {
        removeChild(c.Context, c)
    }
}
  • closedchanが何故closedなのか最初は分からなかったが
  • コードを見つけて分かった
  • initでcloseしていた
var closedchan = make(chan struct{})

func init() {
    close(closedchan)
}
  • ちょいすっきりしたので気が向いたら次回につづく