What's new Context in Go1.14

What's new Context in Go1.14

164fd510e92a1912155b869b2c333c1e?s=128

Tsuji Daishiro

February 25, 2020
Tweet

Transcript

  1. Go1.14のcontextは何が変わるのか 2020/02/25(Tue) Go 1.14 Release Party in Japan Future Tsuji

    Daishiro
  2. Who are you? 辻 大志郎(つじ だいしろう) @d_tutuz 渋谷区役所(~2014/9) Future(2014/10~) ✓

    所属 Technology Innovation Group 競技プログラミング部 2
  3. None
  4. Goの標準パッケージのコードリーディング会やってます ✓ 社内で標準パッケージのコードリーディング会やってます。全12回 ✓ 以下のパッケージ ✓ io ✓ errors ✓

    hash/maphash ✓ context ✓ flag ✓ path ✓ testing ✓ iotest ✓ sort ✓ net/http 1 (client) ✓ net/http 2 (server) ✓ database/sql 4 ←ここまで実施
  5. context

  6. 今日話すこと ✓ Go1.14でcontextは何が変わるのか ✓ contextの基礎(CLを理解するための補足) ✓ contextが改善された背景 ✓ CLの修正内容の概要と詳細 6

  7. Go1.14でcontextは何が変わるのか ✓ contextパッケージの WithCancel と WithTimeout が改善します ✓ Go1.14のRelease Noteには含まれていないのがポイントです

    ✓ mattnさんのツイートを見て知った方も多いのではないでしょうか? ✓ 私もその一人です 7
  8. CLとProposal ✓ 公式のCLとProposalは以下です。 ✓ ProposalのIssueはCLに記載されているので、CLを見ればOKです。 ✓ CL ✓ https://go-review.googlesource.com/c/go/+/196521/ ✓

    Proposal ✓ https://github.com/golang/go/issues/28728 8
  9. 改善する内容をもう少し具体的に Go1.14のcontextはカスタムコンテキストを用いたときに、適切にコンテキストを埋 め込むと、WithCancel や WithTimeout でゴルーチンが生成されなくなりました 9 ✓ 具体的に改善する内容を示します ※カスタムコンテキスト:context.Contextを満たした独自の構造体

  10. contextの基礎

  11. contextの基礎 ✓ contextパッケージの実装を把握されていますか? ✓ context.WithCancel, context.WithValue を用いてコンテキストを生成したときの構造体 ✓ キャンセルの動きとその実装 ✓

    CLで修正されている内容を理解するための補足です 11
  12. contextの基礎 ✓ 以下のようなケースを考えてみます ✓ 起点となるとなる場所で context.Background() としてコンテキストを生成 ✓ Background: emptyCtx

    ✓ そのコンテキストから context.WithValue や context.WithCancel (context.WithTimeout) をしてコンテキストを生成 ✓ WithValue: valueCtx ✓ WithCancal: cancelCtx ✓ WithTimeout: timerCtx 12
  13. それぞれの構造体 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 ←インターフェースの埋め込み(実装上親のコンテキストを保持)
  14. キャンセルをしたときの動き ✓ 親のキャンセルは子に伝播する ✓ 子のキャンセルは親に伝播しない 14

  15. 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)
  16. 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を見つける関数
  17. 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を返す(呼び 出し側でゴルーチンを生成)
  18. contextが改善された背景

  19. 問題の背景 ✓ 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
  20. 問題の背景 ✓ Proposalを提起した @gobwas 氏のコメントです The point is that for

    the performance sensitive applications starting of a separate goroutine could be an issue. ✓ WorkerContext という context.Context インターフェースを満たしたカスタムコンテキストを定 義していて、その場合にパフォーマンスの懸念がある。というのがプロポーザルの背景であると読み 取れます。 20
  21. 具体的な問題例 ✓ WorkerContext から WithCancel でキャンセルできるコンテキストを N つ生成する場合 ✓ Taskはゴルーチンを生成して処理

    ✓ TaskはキャンセルできるようにWithCancelで生成したコンテキストを渡します 21
  22. 想定の挙動 ✓ 以下のように動いている想定・・・だけど実際はそうではない ✓ カスタムコンテキストからWithCancelする場合はゴルーチンが生成されるため 22

  23. 具体的な問題例 ✓ 実際には倍のゴルーチンが背後で動いている ✓ parentCancelCtxの実装のため 23 背後ではcontextパッケージでもゴルーチ ンが N つ生成されている

  24. 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 } } } • 前述したグラフを逆向きに探索して、 キャンセル可能な一番近いコンテキス トを探索する実装 • 親コンテキストがカスタムコンテキスト の場合は常にここに到達する • よって呼び出し元でゴルーチンが生成 される
  25. 問題のまとめ ✓ 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
  26. CLの修正内容の概要と詳細

  27. 修正内容の概要 ✓ 実装は @rsc 氏がコミットしていました。 ✓ https://github.com/golang/go/commit/0ad368675bae1e3228c9146e092cd00cfb29ac27 ✓ CLのメッセージから修正した内容の概要を知ることができます。 27

  28. 修正内容の概要 ✓ 修正概要 ✓ 子コンテキストを親コンテキストに紐付けるときの方法 を変更しています。 ✓ 何が嬉しいか ✓ この修正によって、カスタムコンテキストが条件を満たす場合(*cancelCtx

    をラップする、かつ Done() を想定外の変更をしない場合)には、カスタムコンテキストでもゴルーチンが生成され ずに WithCancel/WithTimeout を用いることができる ようになります。 28
  29. 実装の詳細(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関数が全体的に書き換わっています。
  30. 実装の詳細(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メソッドの実装
  31. テスト(Go1.14) ✓ テストが明快。テストを見るのが一番わかりやすいかも ✓ 以下のグラフを生成してテスト実施 凡例 :cancelCtx :カスタムコンテキスト(Done拡張無) :カスタムコンテキスト(Done拡張有) :timerCtx

    WithCancal WithCancal WithCancal WithCancal WithCancal
  32. テスト(Go1.14) ✓ ゴルーチンが生成されるパターンは1箇所のみ 凡例 :cancelCtx :カスタムコンテキスト(Done拡張無) :カスタムコンテキスト(Done拡張有) :timerCtx Done()を拡張している場合は WithCancel

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

  34. Go1.14でcontextが改善されました Go1.14のcontextはカスタムコンテキストを用いたときに、適切にコンテキストを埋 め込むと、WithCancel や WithTimeout でゴルーチンが生成されなくなりました 34

  35. その他参考 35 ✓ 以下の私のQiitaの記事がベースになっています ✓ https://qiita.com/tutuz/items/963a6118cec63a4cd2f3