Description
Sometimes, randomly, our go GRPC application crash, and at this point we not understand why.
Full error
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x54 pc=0x6c335c]
goroutine 226092 [running]:
net/http/httptrace.ContextClientTrace(...)
/usr/local/go/src/net/http/httptrace/trace.go:25
net/http.(*Request).write(0xc00053f560, 0xe541c0, 0xc0000401c0, 0x1, 0xc000dda090, 0x0, 0x0, 0x0)
/usr/local/go/src/net/http/request.go:548 +0x5c
net/http.(*persistConn).writeLoop(0xc0014766c0)
/usr/local/go/src/net/http/transport.go:2385 +0x1a7
created by net/http.(*Transport).dialConn
/usr/local/go/src/net/http/transport.go:1744 +0xc9c
That's all...No stack trace, no any additional information from where it was called, nothing,
application can work hour, 30 minutes, or 10 minutes and then just got panic and crash
What version of Go are you using (go version
)?
$ go version go1.16 linux/amd64
What operating system and processor architecture are you using (go env
)?
go env
GO111MODULE="" GOARCH="amd64" GOBIN="" GOCACHE="/root/.cache/go-build" GOENV="/root/.config/go/env" GOEXE="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOINSECURE="" GOMODCACHE="/root/work/pkg/mod" GONOPROXY="" GONOSUMDB="" GOOS="linux" GOPATH="/root/work" GOPRIVATE="" GOPROXY="https://proxy.golang.org,direct" GOROOT="/usr/local/go" GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64" GOVCS="" GOVERSION="go1.16" GCCGO="gccgo" AR="ar" CC="gcc" CXX="g++" CGO_ENABLED="1" GOMOD="/var/www/test-api/go.mod" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build1064844775=/tmp/go-build -gno-record-gcc-switches"
What did you do?
Our api work as grpc microservice, which accept incoming request and parse some websites by passed rules.
Main code, which do http requests, I left only the main piece of code, because the other methods like AddHeadersToRequest, AddPreviousHeaders, do not carry any serious logic and only add headers to the request.
request.go
// Client
func getClient() *http.Client {
proxyUrl, err := url.Parse("proxy url here")
if err != nil {
log.Fatal("Proxy error", err.Error())
}
return &http.Client{
Timeout: time.Second * 60,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
Proxy: http.ProxyURL(proxyUrl),
}}
}
// generateClientRequest func
func generateClientRequest(ctx context.Context, method string, s string, body *strings.Reader) (*http.Request, error) {
if body == nil {
return http.NewRequestWithContext(ctx, method, s, nil)
}
return http.NewRequestWithContext(ctx, method, s, body)
}
// Main code. HttpRequest func
func (r *Rule) HTMLRequest(args *Args, values *ValueArguments) {
iterate := args.Iteration(r)
defer iterate.IsStarted()()
var options Request
unmarshalError := json.Unmarshal(r.Data.Value, &options)
if unmarshalError != nil {
iterate.RegisterError(unmarshalError)
return
}
ctx, cancel := context.WithTimeout(context.Background(), getTimeoutForService(r.Service.Timeout))
defer cancel()
endpoint := values.ReplaceStringFromVariables(r.Value)
req, reqError := generateClientRequest(ctx, options.Method, endpoint, options.GetBody(values))
if reqError != nil {
iterate.RegisterError(reqError)
setSystemStatus(args, r, "error")
return
}
// Fix increasing socket connections.
req.Close = true
options.AddHeadersToRequest(req, values)
options.AddPreviousHeaders(req, values)
if req == nil {
log.Fatal("Request is nil...", values.Value, args.Data())
}
if cl == nil {
log.Fatal("Client is nil...", values.Value, args.Data())
}
resp, doError := cl.Do(req)
if resp != nil {
defer func() {
err := resp.Body.Close()
if err != nil {
iterate.RegisterError(err)
setSystemStatus(args, r, "error")
fmt.Printf("Defer[336]: %s\n", err.Error())
}
}()
}
if doError != nil {
fmt.Println("Error here", doError.Error())
iterate.RegisterError(doError)
setSystemStatus(args, r, "error")
return
}
// Oops, we have nil response, that mean we need stop here.
if resp == nil {
fmt.Println("Response is nil", endpoint)
iterate.RegisterError(errors.New("response is nil for" + endpoint))
setSystemStatus(args, r, "error")
return
}
stringStatusCode := fmt.Sprintf("%d", resp.StatusCode)
ok := setResponseStatus(r, args, stringStatusCode)
if !ok {
return
}
utf8, readerError := charset.NewReader(resp.Body, resp.Header.Get("Content-Type"))
if readerError != nil {
iterate.RegisterError(readerError)
setSystemStatus(args, r, "error")
return
}
bts, readError := ioutil.ReadAll(utf8)
if readError != nil {
iterate.RegisterError(readError)
setSystemStatus(args, r, "error")
return
}
}
Almost everywhere i put defer with recover function for get all info what i can, but in panic log's i newer seen what he executed, and didn't see panic messages in sentry, i call it like that:
SentryRecover
func SentryRecover(data interface{}) func() {
return func() {
err := recover()
if err != nil {
fmt.Println(err)
fmt.Println(string(debug.Stack()))
fmt.Println(data)
sentry.WithScope(func(scope *sentry.Scope) {
scope.SetExtra("Details", data)
sentry.CurrentHub().Recover(err)
})
}
}
}
defer helpers.SentryRecover(map[string]string{
"type": "MainCaller",
"value": arguments.Value,
})()
What did you expect to see?
At least more information log, with stack trace, so that I could understand where this error starts from
What did you see instead?
Panic, and strange error without any additional information