use gdb investigate memory leak
Sep 12, 2016我們先來看一下 Rob Pike 在 Google I/O 2012 的 talk “Go Concurrency Patterns” 中,有一段 snippet 會造成 momery leak。
先解釋一下這段 code,First 吃兩個 parameters,很明顯的 query 是個字串,
replicas 則是一個 slice of Search,那 Search 是什麼?這邊底下有看到
replicas[i](query)
,得知他是一個 func 而且還接受一個 string parameter。
那他為什麼會 momery leak?
我們看到有一個 for range loop 在 iterate replicas,for each replica 都建立一個
goroutine 讓他去執行 search 的工作,當她完成了他會把結果塞進一個 type 是 Result
的 channel,在 func First 的結尾有一個 <-c
在 block,直到有一個 goroutine 把
Result 塞進 channel,所以先回來的 goroutine 就會是 func First 的 return
value,但是後回來的就會是一個 deadlock 沒有一個 channel 等待接收他的結果,因為
func First 已經結束了。
func First(query string, replicas ...Search) Result {
c := make(chan Result)
searchReplica := func(i int) { c <- replicas[i](query) }
for i := range replicas {
go searchReplica(i)
}
return <-c
}
我們要怎麼知道他真的 momery leak 了?
我們要用 gdb!
在用 gdb 之前,我們先要有一個完整可執行的範例程式
package main
import (
"fmt"
"log"
"net/http"
"time"
)
type search func(query string) result
type result struct {
provider string
status int
}
func main() {
replicas := []search{google, yahoo}
r := first("lovebugs", replicas...)
fmt.Println(r)
time.Sleep(2 * time.Second)
fmt.Println("woke up")
}
func google(query string) result {
res, err := http.Get("http://google.com?q=" + query)
if err != nil {
log.Fatal(err)
}
return result{
provider: "google",
status: res.StatusCode,
}
}
func yahoo(query string) result {
res, err := http.Get("http://yahoo.com/search?p=" + query)
if err != nil {
log.Fatal(err)
}
return result{
provider: "yahoo",
status: res.StatusCode,
}
}
func first(query string, replicas ...search) result {
c := make(chan result)
searchReplica := func(i int) {
c <- replicas[i](query)
}
for i := range replicas {
go searchReplica(i)
}
return <-c
}
編譯他 go build -gcflags "-N -l" main.go
用 gdb
執行他 gdb main
看到下面這個畫面就是我們開始執行了
GNU gdb (GDB) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-apple-darwin15.5.0".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from main...done.
Loading Go Runtime support.
(gdb)
先把我們要的 breakpoint 下好
(gdb) b 51
Breakpoint 1 at 0x29a5: file /Users/nukr/workspace/gocode/src/github.com/nukr/golang_test/blocked_goroutines_and_resource_lea
ks/leaks/main.go, line 51.
(gdb) b 22
Breakpoint 2 at 0x221b: file /Users/nukr/workspace/gocode/src/github.com/nukr/golang_test/blocked_goroutines_and_resource_lea
ks/leaks/main.go, line 22.
(gdb)
第一個我們要看的是 goroutine 在執行的時候是在哪個位置,這樣才能知道在 func main 結尾的時候,他的執行狀況。
我們下 run
(gdb) run
Starting program: /Users/nukr/workspace/gocode/src/github.com/nukr/golang_test/blocked_goroutines_and_resource_leaks/leaks/ma
in
[New Thread 0x126f of process 9680]
[New Thread 0x1403 of process 9680]
[New Thread 0x1503 of process 9680]
[New Thread 0x1603 of process 9680]
Thread 1 hit Breakpoint 1, main.first.func1 (i=1)
at /Users/nukr/workspace/gocode/src/github.com/nukr/golang_test/blocked_goroutines_and_resource_leaks/leaks/main.go:51
51 c <- replicas[i](query)
用 info goroutine
看一下
(gdb) info goroutines
1 waiting runtime.gopark
* 17 syscall runtime.goexit
2 waiting runtime.gopark
3 waiting runtime.gopark
18 waiting runtime.gopark
* 19 running main.first.func1
* 20 running main.first.func1
我們看到 19, 20 這兩個就是我們的兩個 goroutine
再下 c
(gdb) c
Continuing.
[Switching to Thread 0x1403 of process 9680]
Thread 3 hit Breakpoint 1, main.first.func1 (i=0)
at /Users/nukr/workspace/gocode/src/github.com/nukr/golang_test/blocked_goroutines_and_resource_leaks/leaks/main.go:51
51 c <- replicas[i](query)
用 info goroutine
看現在 goroutine 的狀況
再下 c
到 fmt.Println("woke up")
用 info goroutine
確認最後的 goroutine 狀況
(gdb) info goroutines
* 1 running runtime.gopark
* 17 syscall runtime.goexit
2 waiting runtime.gopark
3 waiting runtime.gopark
18 waiting runtime.gopark
20 waiting runtime.gopark
41 waiting runtime.gopark
* 4 syscall runtime.systemstack_switch
38 waiting runtime.gopark
33 waiting runtime.gopark
50 waiting runtime.gopark
37 waiting runtime.gopark
51 waiting runtime.gopark
39 waiting runtime.gopark
36 waiting runtime.gopark
10 waiting runtime.gopark
31 waiting runtime.gopark
42 waiting runtime.gopark
32 waiting runtime.gopark
66 waiting runtime.gopark
我們看到我們等了兩秒了,但是 20 的 goroutine 還在 memory leak comfirm!