Upgrade to Pro — share decks privately, control downloads, hide ads and more …

What's new Context in Go1.14

Tsuji Daishiro
February 25, 2020

What's new Context in Go1.14

Tsuji Daishiro

February 25, 2020
Tweet

More Decks by Tsuji Daishiro

Other Decks in Technology

Transcript

  1. Goの標準パッケージのコードリーディング会やってます ✓ 社内で標準パッケージのコードリーディング会やってます。全12回 ✓ 以下のパッケージ ✓ io ✓ errors ✓

    hash/maphash ✓ context ✓ flag ✓ path ✓ testing ✓ iotest ✓ sort ✓ net/http 1 (client) ✓ net/http 2 (server) ✓ database/sql 4 ←ここまで実施
  2. contextの基礎 ✓ 以下のようなケースを考えてみます ✓ 起点となるとなる場所で context.Background() としてコンテキストを生成 ✓ Background: emptyCtx

    ✓ そのコンテキストから context.WithValue や context.WithCancel (context.WithTimeout) をしてコンテキストを生成 ✓ WithValue: valueCtx ✓ WithCancal: cancelCtx ✓ WithTimeout: timerCtx 12
  3. それぞれの構造体 13 type valueCtx struct { Context key, val interface{}

    } type cancelCtx struct { Context mu sync.Mutex done chan struct{} children map[canceler]struct{} err error } ・cancelCtx ・valueCtx ←インターフェースの埋め込み(実装上親のコンテキストを保持) ←子のコンテキストをキャンセルできるようにグラフを 生成するmap ←インターフェースの埋め込み(実装上親のコンテキストを保持)
  4. WithCancelの実装(Go1.13) 15 func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {

    c := newCancelCtx(parent) propagateCancel(parent, &c) return &c, func() { c.cancel(true, Canceled) } } キャンセルを伝播するグラフを生成 ✓ context.WithCancelでコンテキストを生成するときの実装(Go1.13)
  5. propagateCancelの実装(Go1.13) 16 func propagateCancel(parent Context, child canceler) { if parent.Done()

    == nil { return // parent is never canceled } if p, ok := parentCancelCtx(parent); ok { p.mu.Lock() if p.err != nil { // parent has already been canceled child.cancel(false, p.err) } else { if p.children == nil { p.children = make(map[canceler]struct{}) } p.children[child] = struct{}{} } p.mu.Unlock() } else { go func() { select { case <-parent.Done(): child.cancel(false, parent.Err()) case <-child.Done(): } }() } } • キャンセルのグラフを生成する時、親 のcancelCtxを見つける必要がある • 親のcancelCtxのmapに子を紐付け る or • ゴルーチンでコンテキストを監視する ポイントを生成する(親コンテキストがキャン セルされたときは子にキャンセルを伝播する必 要がある) • parentCancelCtx 関数は 親となるcancelCtxを見つける関数
  6. parentCancelCtxの実装(Go1.13) 17 func parentCancelCtx(parent Context) (*cancelCtx, bool) { for {

    switch c := parent.(type) { case *cancelCtx: return c, true case *timerCtx: return &c.cancelCtx, true case *valueCtx: parent = c.Context default: return nil, false } } } • 型スイッチをしながら、キャンセルできる コンテキスト(cancelCtx)が出現する までコンテキストのグラフを逆向きにた どる。最初に到達した一番近いコンテ キストを取得 • valueCtxの場合は埋め込みされてい るコンテキストを取得して、再度親のコ ンテキストで型アサーションをする • 該当しない場合はnil,okを返す(呼び 出し側でゴルーチンを生成)
  7. 問題の背景 ✓ Proposalを見ましょう。 ✓ https://github.com/golang/go/issues/28728 ✓ 上記の Proposal は context.Context

    を満たしたカスタムのコンテキストを用いて処理を実装し ているケースの話です。 This proposal concerns a way to implement custom context.Context type, that is clever enough to cancel its children contexts during cancelation. 19
  8. 問題の背景 ✓ Proposalを提起した @gobwas 氏のコメントです The point is that for

    the performance sensitive applications starting of a separate goroutine could be an issue. ✓ WorkerContext という context.Context インターフェースを満たしたカスタムコンテキストを定 義していて、その場合にパフォーマンスの懸念がある。というのがプロポーザルの背景であると読み 取れます。 20
  9. parentCancelCtxの実装(Go1.13) ※再掲 24 func parentCancelCtx(parent Context) (*cancelCtx, bool) { for

    { switch c := parent.(type) { case *cancelCtx: return c, true case *timerCtx: return &c.cancelCtx, true case *valueCtx: parent = c.Context default: return nil, false } } } • 前述したグラフを逆向きに探索して、 キャンセル可能な一番近いコンテキス トを探索する実装 • 親コンテキストがカスタムコンテキスト の場合は常にここに到達する • よって呼び出し元でゴルーチンが生成 される
  10. 問題のまとめ ✓ Go のコアチームの @rsc 氏が問題をまとめています ✓ https://github.com/golang/go/issues/28728#issuecomment-532793417 To summarize

    the discussion so far, this issue concerns the implementation of context.WithCancel(parent) (or context.WithDeadline, but we can focus on WithCancel). ✓ context.WithCancel(parent) の実装に関する問題と述べています。 The problem addressed by this issue is the creation of one goroutine per WithCancel call, which lasts until the created child is canceled. The goroutine is needed today because we need to watch for the parent to be done. The child is a known implementation; the parent is not. ✓ WithCancel の呼び出しごとにゴルーチンが 1 つ生成されることが問題である 、と述べています。 25
  11. 修正内容の概要 ✓ 修正概要 ✓ 子コンテキストを親コンテキストに紐付けるときの方法 を変更しています。 ✓ 何が嬉しいか ✓ この修正によって、カスタムコンテキストが条件を満たす場合(*cancelCtx

    をラップする、かつ Done() を想定外の変更をしない場合)には、カスタムコンテキストでもゴルーチンが生成され ずに WithCancel/WithTimeout を用いることができる ようになります。 28
  12. 実装の詳細(Go1.14) 29 func parentCancelCtx(parent Context) (*cancelCtx, bool) { done :=

    parent.Done() if done == closedchan || done == nil { return nil, false } p, ok := parent.Value(&cancelCtxKey).(*cancelCtx) if !ok { return nil, false } p.mu.Lock() ok = p.done == done p.mu.Unlock() if !ok { return nil, false } return p, true } • 新しくContextのValue()を用いるよ うになっています。後述しますが、これ でカスタムコンテキストの場合でもキャ ンセル可能なコンテキスト(cancelCtx)を 探索できるようになります。 • Done()メソッドをカスタム実装してい る場合はゴルーチンを生成しない対象 外(チャネル値は同じmakeの呼び出しで生成された場 合 or nil の場合のみ一致) ✓ CLではparentCancelCtx関数が全体的に書き換わっています。
  13. 実装の詳細(Go1.14) 30 var cancelCtxKey int // .. func (c *cancelCtx)

    Value(key interface{}) interface{} { if key == &cancelCtxKey { return c } return c.Context.Value(key) } • 新しくContextのValue()を用いるよ うになっています。keyのアドレスが一 致しない場合はcancelCtx型ではな いので、親コンテキストのValue()を呼 び出します。 • この呼び方で一致するまで逆向きにコ ンテキストを探索できます。 (従来のcontext.WithValueで生成される valueCtx型のValue()でも同様) ✓ cancelCtxに新規に追加されたValueメソッドの実装
  14. テスト(Go1.14) ✓ ゴルーチンが生成されるパターンは1箇所のみ 凡例 :cancelCtx :カスタムコンテキスト(Done拡張無) :カスタムコンテキスト(Done拡張有) :timerCtx Done()を拡張している場合は WithCancel

    の呼び出しでゴ ルーチンが生成される カスタムコンテキストから WithContext を呼び出しても ゴルーチンが生成されない WithCancal WithCancal WithCancal WithCancal WithCancal