Enlace original: Cómo el lenguaje Go detecta fugas de rutinas en las pruebas
prefacio
Hola a todos, soy
asong
;Como todos sabemos,
gorourtine
el diseño del lenguaje es el componente central de la implementación concurrente del lenguaje. Es fácil de usar, peroGo
también se encuentra con varias enfermedades intratables. Entre ellas,goroutine
la fuga es una de las enfermedades gravespprof
Por supuesto,goleak
él está aquí. Es de código abierto por elUber
equipo y se puede usar para detectargoroutine
fugas. Se puede combinar con pruebas unitarias para evitar que suceda. Echemos un vistazo en este artículogoleak
.
fuga de rutina
No sé si te has encontrado con goroutine
fugas en tu desarrollo diario. goroutine
Las fugas son en realidad un goroutine
bloqueo. Estos bloqueados goroutine
sobrevivirán hasta el final del proceso, y la memoria de pila que ocupan no se puede liberar, lo que resulta en cada vez menos memoria disponible en el sistema hasta que se bloquee! Un breve resumen de varias causas comunes de fugas:
Goroutine
La lógica interna entra en un bucle infinito y sigue ocupando recursosGoroutine
Cuando se acoplachannel
/mutex
usa, se ha bloqueado debido a un uso inadecuadoGoroutine
La lógica interna espera mucho tiempo, lo que hace que elGoroutine
número explote
A continuación usamos la combinación clásica de Goroutine
+ para mostrar la fuga;channel
goroutine
func GetData() {
var ch chan struct{}
go func() {
<- ch
}()
}
func main() {
defer func() {
fmt.Println("goroutines: ", runtime.NumGoroutine())
}()
GetData()
time.Sleep(2 * time.Second)
}
复制代码
Este ejemplo se channel
olvida de inicializar, y tanto las operaciones de lectura como las de escritura provocarán un bloqueo. Si este método es para escribir una sola prueba, no podrá detectar el problema:
func TestGetData(t *testing.T) {
GetData()
}
复制代码
resultado de la operación:
=== RUN TestGetData
--- PASS: TestGetData (0.00s)
PASS
复制代码
La prueba integrada no se puede satisfacer, así que vamos a presentarla goleak
para probarla.
metas
github地址:github.com/uber-go/gol…
使用goleak
主要关注两个方法即可:VerifyNone
、VerifyTestMain
,VerifyNone
用于单一测试用例中测试,VerifyTestMain
可以在TestMain
中添加,可以减少对测试代码的入侵,举例如下:
使用VerifyNone
:
func TestGetDataWithGoleak(t *testing.T) {
defer goleak.VerifyNone(t)
GetData()
}
复制代码
运行结果:
=== RUN TestGetDataWithGoleak
leaks.go:78: found unexpected goroutines:
[Goroutine 35 in state chan receive (nil chan), with asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector.GetData.func1 on top of the stack:
goroutine 35 [chan receive (nil chan)]:
asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector.GetData.func1()
/Users/go/src/asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector/main.go:12 +0x1f
created by asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector.GetData
/Users/go/src/asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector/main.go:11 +0x3c
]
--- FAIL: TestGetDataWithGoleak (0.45s)
FAIL
Process finished with the exit code 1
复制代码
通过运行结果看到具体发生goroutine
泄漏的具体代码段;使用VerifyNone
会对我们的测试代码有入侵,可以采用VerifyTestMain
方法可以更快的集成到测试中:
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
复制代码
运行结果:
=== RUN TestGetData
--- PASS: TestGetData (0.00s)
PASS
goleak: Errors on successful test run: found unexpected goroutines:
[Goroutine 5 in state chan receive (nil chan), with asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector.GetData.func1 on top of the stack:
goroutine 5 [chan receive (nil chan)]:
asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector.GetData.func1()
/Users/go/src/asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector/main.go:12 +0x1f
created by asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector.GetData
/Users/go/src/asong.cloud/Golang_Dream/code_demo/goroutine_oos_detector/main.go:11 +0x3c
]
Process finished with the exit code 1
复制代码
VerifyTestMain
的运行结果与VerifyNone
有一点不同,VerifyTestMain
会先报告测试用例执行结果,然后报告泄漏分析,如果测试的用例中有多个goroutine
泄漏,无法精确定位到发生泄漏的具体test,需要使用如下脚本进一步分析:
# Create a test binary which will be used to run each test individually
$ go test -c -o tests
# Run each test individually, printing "." for successful tests, or the test name
# for failing tests.
$ for test in $(go test -list . | grep -E "^(Test|Example)"); do ./tests -test.run "^$test\$" &>/dev/null && echo -n "." || echo -e "\n$test failed"; done
复制代码
这样会打印出具体哪个测试用例失败。
goleak实现原理
从VerifyNone
入口,我们查看源代码,其调用了Find
方法:
// Find looks for extra goroutines, and returns a descriptive error if
// any are found.
func Find(options ...Option) error {
// 获取当前goroutine的ID
cur := stack.Current().ID()
opts := buildOpts(options...)
var stacks []stack.Stack
retry := true
for i := 0; retry; i++ {
// 过滤无用的goroutine
stacks = filterStacks(stack.All(), cur, opts)
if len(stacks) == 0 {
return nil
}
retry = opts.retry(i)
}
return fmt.Errorf("found unexpected goroutines:\n%s", stacks)
}
复制代码
我们在看一下filterStacks
方法:
// filterStacks will filter any stacks excluded by the given opts.
// filterStacks modifies the passed in stacks slice.
func filterStacks(stacks []stack.Stack, skipID int, opts *opts) []stack.Stack {
filtered := stacks[:0]
for _, stack := range stacks {
// Always skip the running goroutine.
if stack.ID() == skipID {
continue
}
// Run any default or user-specified filters.
if opts.filter(stack) {
continue
}
filtered = append(filtered, stack)
}
return filtered
}
复制代码
这里主要是过滤掉一些不参与检测的goroutine stack
,如果没有自定义filters
,则使用默认的filters
:
func buildOpts(options ...Option) *opts {
opts := &opts{
maxRetries: _defaultRetries,
maxSleep: 100 * time.Millisecond,
}
opts.filters = append(opts.filters,
isTestStack,
isSyscallStack,
isStdLibStack,
isTraceStack,
)
for _, option := range options {
option.apply(opts)
}
return opts
}
复制代码
从这里可以看出,默认检测20
次,每次默认间隔100ms
;添加默认filters
;
总结一下goleak
的实现原理:
Utilice el runtime.Stack()
método para obtener toda la información de la pila que se está ejecutando actualmente goroutine
. De forma predeterminada, los elementos de filtro que no necesitan ser detectados se definen de forma predeterminada. El número de detecciones + intervalo de detección se define de forma predeterminada, y la detección se realiza periódicamente. Finalmente , después de múltiples controles, no se encuentran los restantes, y goroutine
se juzga que no hay goroutine
fuga. .
Resumir
En este artículo, compartimos una herramienta que puede encontrar goroutine
fugas en las pruebas, pero aún necesita un soporte completo de casos de prueba, lo que expone la importancia de los casos de prueba Amigos, las buenas herramientas pueden ayudarnos a encontrar problemas más rápido, pero el código La calidad aún está en nuestras propias manos, vamos, muchachos~.
Bueno, aquí termina este artículo, soy una canción , hasta la próxima.
Bienvenido a la cuenta pública: Golang Dream Factory
Referencias