Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

Who are you? 辻 大志郎(つじ だいしろう) @d_tutuz 渋谷区役所(~2014/9) Future(2014/10~) ✓ 所属 Technology Innovation Group 競技プログラミング部 2

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Goの標準パッケージのコードリーディング会やってます ✓ 社内で標準パッケージのコードリーディング会やってます。全12回 ✓ 以下のパッケージ ✓ io ✓ errors ✓ hash/maphash ✓ context ✓ flag ✓ path ✓ testing ✓ iotest ✓ sort ✓ net/http 1 (client) ✓ net/http 2 (server) ✓ database/sql 4 ←ここまで実施

Slide 5

Slide 5 text

context

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Go1.14でcontextは何が変わるのか ✓ contextパッケージの WithCancel と WithTimeout が改善します ✓ Go1.14のRelease Noteには含まれていないのがポイントです ✓ mattnさんのツイートを見て知った方も多いのではないでしょうか? ✓ 私もその一人です 7

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

contextの基礎

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

contextの基礎 ✓ 以下のようなケースを考えてみます ✓ 起点となるとなる場所で context.Background() としてコンテキストを生成 ✓ Background: emptyCtx ✓ そのコンテキストから context.WithValue や context.WithCancel (context.WithTimeout) をしてコンテキストを生成 ✓ WithValue: valueCtx ✓ WithCancal: cancelCtx ✓ WithTimeout: timerCtx 12

Slide 13

Slide 13 text

それぞれの構造体 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 ←インターフェースの埋め込み(実装上親のコンテキストを保持)

Slide 14

Slide 14 text

キャンセルをしたときの動き ✓ 親のキャンセルは子に伝播する ✓ 子のキャンセルは親に伝播しない 14

Slide 15

Slide 15 text

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)

Slide 16

Slide 16 text

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を見つける関数

Slide 17

Slide 17 text

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を返す(呼び 出し側でゴルーチンを生成)

Slide 18

Slide 18 text

contextが改善された背景

Slide 19

Slide 19 text

問題の背景 ✓ 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

Slide 20

Slide 20 text

問題の背景 ✓ Proposalを提起した @gobwas 氏のコメントです The point is that for the performance sensitive applications starting of a separate goroutine could be an issue. ✓ WorkerContext という context.Context インターフェースを満たしたカスタムコンテキストを定 義していて、その場合にパフォーマンスの懸念がある。というのがプロポーザルの背景であると読み 取れます。 20

Slide 21

Slide 21 text

具体的な問題例 ✓ WorkerContext から WithCancel でキャンセルできるコンテキストを N つ生成する場合 ✓ Taskはゴルーチンを生成して処理 ✓ TaskはキャンセルできるようにWithCancelで生成したコンテキストを渡します 21

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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 } } } • 前述したグラフを逆向きに探索して、 キャンセル可能な一番近いコンテキス トを探索する実装 • 親コンテキストがカスタムコンテキスト の場合は常にここに到達する • よって呼び出し元でゴルーチンが生成 される

Slide 25

Slide 25 text

問題のまとめ ✓ 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

Slide 26

Slide 26 text

CLの修正内容の概要と詳細

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

修正内容の概要 ✓ 修正概要 ✓ 子コンテキストを親コンテキストに紐付けるときの方法 を変更しています。 ✓ 何が嬉しいか ✓ この修正によって、カスタムコンテキストが条件を満たす場合(*cancelCtx をラップする、かつ Done() を想定外の変更をしない場合)には、カスタムコンテキストでもゴルーチンが生成され ずに WithCancel/WithTimeout を用いることができる ようになります。 28

Slide 29

Slide 29 text

実装の詳細(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関数が全体的に書き換わっています。

Slide 30

Slide 30 text

実装の詳細(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メソッドの実装

Slide 31

Slide 31 text

テスト(Go1.14) ✓ テストが明快。テストを見るのが一番わかりやすいかも ✓ 以下のグラフを生成してテスト実施 凡例 :cancelCtx :カスタムコンテキスト(Done拡張無) :カスタムコンテキスト(Done拡張有) :timerCtx WithCancal WithCancal WithCancal WithCancal WithCancal

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

まとめ

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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