Slide 1

Slide 1 text

Go 1.26 リリースパーティ goroutine leak profileについて The Go gopher was designed by Renee French

Slide 2

Slide 2 text

もくじ ● goroutine leak profileとは ● goroutine leakの検出方法 ● goroutine leakを検出できないケース ● まとめ

Slide 3

Slide 3 text

自己紹介 渋谷拓真 Go Conference メインオーガナイザー OSS ○ Kubernetes メンテナー ○ Argo CD メンテナー Kubernetes 2025 Contributor Award 3Dプリンターにハマってます

Slide 4

Slide 4 text

goroutine leak profileとは

Slide 5

Slide 5 text

goroutine leak profileとは Go 1.26で追加された新しい pprof profile ● CL#688335 ● ブロックされており復帰不可能な goroutineを検出する ● GCの到達可能性を利用する ● 1.26ではGOEXPERIMENTで、1.27からはデフォルトになる予定

Slide 6

Slide 6 text

goroutine leak profileとは GOEXPERIMENT=goroutineleakprofile go build -o myapp ● net/http/pprof ● runtime/pprof goroutine 123 [chan send] (leaked): main.leaky.func1() /path/to/main.go:25 +0x1c created by main.leaky in goroutine 1 /path/to/main.go:24 +0x2c

Slide 7

Slide 7 text

goroutine leakの検出方法

Slide 8

Slide 8 text

goroutine leak profileの検出方法 内部でgoroutineleak profileがリクエストされた時にトリガーされる net/httpでもruntime/pprofでも(&Profile).WriteToが呼ばれる var goroutineLeakProfile = &Profile{ name: "goroutineleak", count: runtime_goroutineleakcount, write: writeGoroutineLeak, }

Slide 9

Slide 9 text

writeGoroutineLeak リーク検出用の GCをトリガーする関数を呼ぶ runtime_goroutineLeakGC() //golinkename runtime_goroutineLeakGC runtime.goroutineLeakGC func runtime_goroutineLeakGC()

Slide 10

Slide 10 text

runtime_goroutineLeakGC goroutineLeakを確認するチェックをつける func goroutineLeakGC() { work.goroutineLeak.pending.Store(true) for work.goroutineLeak.pending.Load() { GC() } }

Slide 11

Slide 11 text

なぜGCに組み込むのか リーク検出は特定の goroutineを操作できない状態 ● GCは到達不可能なオブジェクトを削除する ● GCのマークを活用することで到達可能性を検出できる

Slide 12

Slide 12 text

しかしそのまま使いまわせない Goは内部でallgsという変数から全ての goroutineに到達できてしまう ● waiting(*sudog)を使用してブロッキングかどうかは確認できてしまう ● ブロッキングでも参照できてしまいリークかどうかわからなくなる allgs → blocking g → sudog → c → blocking channel

Slide 13

Slide 13 text

maybeTraceablePtrの導入 リーク検出時の GCでは追跡を行わないようにポインタを nilにする ● nilのため以降の GCで追跡対象に含めないようにする ● リーク検出後にマークして追跡対象に戻すため回収されてしまうリスクは 回避している

Slide 14

Slide 14 text

gcPrepareMarkToots goroutine leakを検出する場合はソートして先頭実行可能・末尾ブロック中 に並べ替える if work.goroutineLeak.enabled { work.stackRoots, work.nMaybeRunnableStackRoots = allGsSnapshotSortedForGC() } else { work.stackRoots = allGsSnapshot() work.nMaybeRunnableStackRoots = len(work.stackRoots) }

Slide 15

Slide 15 text

findGoroutineLeaks マーク完了後に Eventually Runnableでなければリークとみなす if findMaybeRunnableGoroutines() { return false } for i := work.nMaybeRunnableStackRoots; i < work.nStackRoots; i++ { gp := work.stackRoots[i] casgstatus(gp, _Gwaiting, _Gleaked) shadePrimitivesOnLeak(gp) }

Slide 16

Slide 16 text

Eventually Runnableとは 実行可能な sudogのchannelがマークされている ● 実行可能な goroutineがアクセスできる ● リークを疑われていたがリークではないと考える

Slide 17

Slide 17 text

Eventually Runnableとは 実行可能な sudogのchannelがマークされている ● 実行可能な goroutineがアクセスできる ● リークを疑われていたがリークではないと考える

Slide 18

Slide 18 text

findGoroutineLeaks sharedPrimitivesOnLeakでリーク対象もマーキングして回収防止 if findMaybeRunnableGoroutines() { return false } for i := work.nMaybeRunnableStackRoots; i < work.nStackRoots; i++ { gp := work.stackRoots[i] casgstatus(gp, _Gwaiting, _Gleaked) shadePrimitivesOnLeak(gp) }

Slide 19

Slide 19 text

goroutine leakを検出できないケース

Slide 20

Slide 20 text

一部ケースは検出できない 銀の弾丸ではない ● グローバル変数に定義されている ● グローバルはマークされてしまう ○ Eventually Runnableと判定 var ch = make(chan int) func leaky() { go func() { ch <- 42 }() }

Slide 21

Slide 21 text

一部ケースは検出できない 銀の弾丸ではない ● ローカル変数で使用されている ● fがforで実行されて runnable ○ Eventually Runnableと判定 ● forのなかでchのselectがあったら ○ waitingとなり検出される func leaky() { ch := make(chan int) go f(ch) go func() { ch <- 42 }() } func f(ch chan int) { for {} }

Slide 22

Slide 22 text

まとめ

Slide 23

Slide 23 text

まとめ ● Go1.26からgoroutine leakを検出できるようになった ● GCの持つ到達可能性の追跡を活用している ● uberのgoleakと併用して lekaを防止しよう!

Slide 24

Slide 24 text

ありがとうございました!

Slide 25

Slide 25 text

おしらせ