SetGoroutineName

в 12:36, , рубрики: Go, высокая производительность, отладка

В Go может быть запущенно больше миллиона goroutine, и этот механизм вызывает восторг до того момента, пока не появляется какая-либо проблема. Например, утечка памяти или dead-lock.

Первое, что хочется сделать это разобраться, посмотреть на то, что же происходит.
image

Это можно сделать так.

package main

import (
    "bytes"
    "fmt"
    "runtime/pprof"
)

func main() {
    profiler := pprof.Lookup("goroutine")
    var buf bytes.Buffer
    profiler.WriteTo(&buf, 1)
    fmt.Println(buf.String())
}

goroutine profile: total 1
1 @ 0xb1620 0xb1340 0xac9c0 0x200c0 0x59f80 0x98d61
#   0xb161f runtime/pprof.writeRuntimeProfile+0xdf  /usr/local/go/src/runtime/pprof/pprof.go:614
#   0xb133f runtime/pprof.writeGoroutine+0x9f   /usr/local/go/src/runtime/pprof/pprof.go:576
#   0xac9bf runtime/pprof.(*Profile).WriteTo+0xff   /usr/local/go/src/runtime/pprof/pprof.go:298
#   0x200bf main.main+0xbf              /tmp/sandbox627252447/main.go:12
#   0x59f7f runtime.main+0x39f          /usr/local/go/src/runtime/proc.go:183

https://play.golang.org/p/ysl_avotLS

Или так:

package main

import (
    "fmt"
    "runtime"
)

func main() {
    b1 := make([]byte, 1<<20)
    runtime.Stack(b1, true)
    s1 := string(b1)
    fmt.Println(s1)
}

goroutine 1 [running]:
main.main()
    /tmp/sandbox952340390/main.go:10 +0x80

https://play.golang.org/p/FCbzn2_DlQ

Мы видим, что в данных примерах запущенна одна гоурутина и ее стек трейс, но если у нас 20-30 одинаковых goroutine, то возникает вопрос кого они обслуживают? Если это сайт, то может быть пользователь, который ушел, а goroutine не прекратила своё существование, и как следствие не освобождает память?

Появляется желание давать гоурутинам названия и в название вписать свякую полезную инфу, как например Id пользователя, которого она обслуживаниет. Но в Go непросто так нет функции SetGoroutineName или GetGoroutineId. Такие функции — зло по мнению создателей языка Go. Трудно не согласиться с такими людми как Rob и Ken. Но зло это не потому, что вообще эти функции зло, а потому, что эти функции программисты начнут использовать неправильно. Не для отладки приложений в момент утечки памяти, а просто как goroutine local storage. То есть, в логике программы плохо использовать такие штуки, но если накосячили, то все же ошибку надо как-то найти.

PyraDebug

Значит нужно давать имена горутинам, но что-бы логика приложения от этого не зависела. Так и родилась pyradebug

go get -u github.com/CossackPyra/pyradebug

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/CossackPyra/pyradebug"
)

var pyraDebug *pyradebug.PyraDebug

func main() {
    pyraDebug = pyradebug.InitPyraDebug()
    pyraDebug.Enable = true

    for i := 0; i < 10; i++ {
        go func(i int) {
            pyraDebug.SetGoroutineName(fmt.Sprintf("sleep func %d", i))
            for {
                time.Sleep(time.Second)
            }
        }(i)
    }

    http.HandleFunc("/goroutine", handle_goroutine)
    log.Fatal(http.ListenAndServe(":8765", nil))

}

func handle_goroutine(w http.ResponseWriter, r *http.Request) {
    agi := pyraDebug.ListGoroutines(1<<20, true)
    w.Header().Set("Content-Type", "text/plain")
    for i, gi := range agi {
        fmt.Fprintf(w, "%dt%st%st%sn", i, gi.Id, gi.Name, gi.Status)
    }
}

# http://127.0.0.1:8765/goroutine

0   goroutine 31        running
1   goroutine 1     IO wait
2   goroutine 17        syscall, locked to thread
3   goroutine 20    sleep func 0    sleep
4   goroutine 21    sleep func 1    sleep
5   goroutine 22    sleep func 2    sleep
6   goroutine 23    sleep func 3    sleep
7   goroutine 24    sleep func 4    sleep
8   goroutine 25    sleep func 5    sleep
9   goroutine 26    sleep func 6    sleep
10  goroutine 27    sleep func 7    sleep
11  goroutine 28    sleep func 8    sleep
12  goroutine 29    sleep func 9    sleep

Важной особенностью библиотеки является pyraDebug.Enable = true, то есть после отладки вы просто ее отключаете и она перестает что-либо делать и значит полагаться на SetGoroutineName в логике нельзя.

Изначально я хотел указывать еще имя гоурутины, что породила её, но на практике мне достаточно только имя, исходный код 127 строки. Его легко форкнуть, разобраться и доработать.

Библитека зависит от текстового формата, который выдает runtime.Stack и может перестать работать в будущем, что так-же хорошо, чтобы разработчики не полагались бездумно на эту библитеку и использовали ее только для отладки.

PS. Всегда интересно узнать, что сделал велосепед

https://github.com/CossackPyra/pyradebug

Автор: pyra

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js