Was kann passieren, wenn ich die Antwort nicht schließe?


93

In Go habe ich einige http-Antworten und vergesse manchmal anzurufen:

resp.Body.Close()

Was passiert in diesem Fall? Wird es einen Speicherverlust geben? Ist es auch sicher, defer resp.Body.Close()sofort nach Erhalt des Antwortobjekts einzugeben?

client := http.DefaultClient
resp, err := client.Do(req)
defer resp.Body.Close()
if err != nil {
    return nil, err
}

Was ist, wenn ein Fehler vorliegt, der Null sein könnte respoder resp.Bodysein könnte ?


Es ist in Ordnung, defer resp.Body.Close () nach err! = Null zu setzen, wenn return vorhanden ist, da der Fehler bereits geschlossen wird, wenn er nicht null ist. Andererseits muss der Body explizit geschlossen werden, wenn die Anforderung erfolgreich ist.
Vasantha Ganesh K

Antworten:


104

Was passiert in diesem Fall? Wird es einen Speicherverlust geben?

Es ist ein Ressourcenleck. Die Verbindung wird nicht wiederverwendet und kann offen bleiben. In diesem Fall wird der Dateideskriptor nicht freigegeben.

Ist es auch sicher, defer resp.Body.Close () sofort nach Erhalt des Antwortobjekts einzugeben?

Nein, folgen Sie dem Beispiel in der Dokumentation und schließen Sie es sofort, nachdem Sie den Fehler überprüft haben.

client := http.DefaultClient
resp, err := client.Do(req)
if err != nil {
    return nil, err
}
defer resp.Body.Close()

Aus der http.ClientDokumentation:

Wenn der zurückgegebene Fehler Null ist, enthält die Antwort einen Body ohne Null, den der Benutzer voraussichtlich schließen wird. Wenn der Body nicht sowohl in EOF gelesen als auch geschlossen wird, kann der zugrunde liegende RoundTripper des Clients (normalerweise Transport) eine dauerhafte TCP-Verbindung zum Server möglicherweise nicht für eine nachfolgende "Keep-Alive" -Anforderung wiederverwenden.


4
Laut diesem Link ist es weiterhin möglich, die Verbindung mit Ihrem Code zu verlieren. Es gibt einige Fälle, in denen die Antwort nicht Null ist und der Fehler nicht Null ist.
mmcdole

12
@mmcdole: Dieser Beitrag ist einfach falsch und es gibt keine Garantie dafür, dass er nicht in Panik gerät, da die Antwort auf einen Fehler keinen definierten Status hat. Wenn ein Body aufgrund eines Fehlers nicht geschlossen wird, handelt es sich um einen Fehler, der gemeldet werden muss. Sie sollten sich an die offizielle Kundendokumentation halten , in der "Bei einem Fehler kann jede Antwort ignoriert werden" und nicht an einen zufälligen Blog-Beitrag.
JimB

Danke für die Klarstellung. Ich hatte einen Fehlerbericht in meiner Go-Bibliothek protokolliert, der auf diese Quelle verwies, und ich hatte ihn nicht überprüft. Interessanterweise gab es anscheinend eine Diskussion über genau diese Quelle auf der Golang-Mailingliste. Es sieht so aus, als ob die Zeile, auf die Sie in den Dokumenten verwiesen haben, kürzlich in 1.6.2 hinzugefügt wurde. Ich gehe also davon aus, dass hier die Verwirrung für den ursprünglichen Blogger entstanden ist, der den Beitrag geschrieben hat.
mmcdole

@mmcdole, diese bestimmte Zeile wurde kürzlich hinzugefügt, um das Problem so eindeutig wie möglich zu gestalten. Es wurde immer wie in den offiziellen Beispielen gezeigt dokumentiert.
JimB

2
@ del-boy: Wenn Sie erwarten, dass dieser Client mehr Anfragen stellt, sollten Sie versuchen, den Text zu lesen, damit die Verbindung wiederverwendet werden kann. Wenn Sie die Verbindung nicht benötigen, lesen Sie den Körper nicht. Wenn Sie den Körper lesen, wickeln Sie ihn mit ein io.LimitReader. Normalerweise verwende ich ein relativ kleines Limit, da es schneller ist, eine neue Verbindung herzustellen, wenn die Anfrage zu groß ist.
JimB

13

Wenn Response.Bodynicht mit Close()Methode geschlossen wird, werden Ressourcen, die einem fd zugeordnet sind, nicht freigegeben. Dies ist ein Ressourcenleck.

Schließen Response.Body

Aus der Antwortquelle :

Es liegt in der Verantwortung des Anrufers, Body zu schließen.

Es sind also keine Finalizer an das Objekt gebunden und es muss explizit geschlossen werden.

Fehlerbehandlung und verzögerte Bereinigung

Bei einem Fehler kann jede Antwort ignoriert werden. Eine Nicht-Null-Antwort mit einem Nicht-Null-Fehler tritt nur auf, wenn CheckRedirect fehlschlägt, und selbst dann ist die zurückgegebene Antwort bereits geschlossen.

resp, err := http.Get("http://example.com/")
if err != nil {
    // Handle error if error is non-nil
}
defer resp.Body.Close() // Close body only if response non-nil

4
Sie sollten beachten, dass sie innerhalb Ihrer Fehlerbehandlungsbedingung zurückkehren sollten. Dies führt zu einer Panik, wenn der Benutzer bei der Fehlerbehandlung nicht zurückkehrt.
Apfelholz

2

Zunächst wird der Deskriptor nie geschlossen, wie oben erwähnt.

Und außerdem speichert Golang die Verbindung zwischen (wobei persistConnstruct zum Umbrechen verwendet wird ), um sie wiederzuverwenden , wenn sie DisableKeepAlivesfalsch ist.

In der Golang-After-Use- client.DoMethode führt go die Goroutine- readLoopMethode als einen der Schritte aus.

In golang http transport.gowird a pconn(persistConn struct)erst dann in den idleConnKanal eingefügt , wenn die Anforderung in der readLoopMethode abgebrochen wurde , und auch diese Goroutine ( readLoopMethode) wird blockiert, bis die Anforderung abgebrochen wird.

Hier ist der Code , der es zeigt.

Wenn Sie mehr wissen möchten, müssen Sie die readLoopMethode sehen.


1

Siehe https://golang.org/src/net/http/client.go.
"Wenn err null ist, enthält resp immer einen Nicht-null resp.Body."

aber sie sagen nicht, wenn err! = nil, resp ist immer nil. Sie fahren fort:
"Wenn resp.Body nicht geschlossen ist, kann der zugrunde liegende RoundTripper des Clients (normalerweise Transport) eine dauerhafte TCP-Verbindung zum Server möglicherweise nicht für eine nachfolgende" Keep-Alive "-Anforderung wiederverwenden."

Daher habe ich das Problem normalerweise folgendermaßen gelöst:

client := http.DefaultClient
resp, err := client.Do(req)
if resp != nil {
   defer resp.Body.Close()
}
if err != nil {
    return nil, err 
}

2
Dies ist falsch und es gibt keine Garantie dafür, dass resp.Body bei einem Fehler null ist.
JimB

Danke @JimB. Der Wortlaut in den Dokumenten lautet "Bei einem Fehler kann jede Antwort ignoriert werden." Es wäre genauer zu sagen: "Bei einem Fehler ist der Antworttext immer geschlossen."
Candita

1
Nein, da normalerweise kein Antwortkörper geschlossen werden muss. Wenn Sie diesen Absatz in den Dokumenten weiterlesen - "Eine Nicht-Null-Antwort mit einem Nicht-Null-Fehler tritt nur auf, wenn CheckRedirect fehlschlägt, und selbst dann ist der zurückgegebene Response.Body bereits geschlossen."
JimB

0

Könnte mir jemand sagen, wenn die Verbindung nicht ohne Schließen wiederverwendet werden kann, warum die Ausgabe von

package main

import (
    "context"
    "log"
    "net"
    "net/http"
)

func main() {
    connections := []net.Conn{}
    go func() {
        server := &http.Server{
            Addr: "127.0.0.1:8080",
            Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.WriteHeader(http.StatusOK)
            }),
            ConnContext: func(ctx context.Context, c net.Conn) context.Context {
                connections = append(connections, c)
                return ctx
            },
        }
        log.Fatal(server.ListenAndServe())
    }()

    if _, err := http.Get("http://127.0.0.1:8080"); err != nil {
        log.Fatal(err)
    }
    log.Printf("first request done, %d connection", len(connections))
    if _, err := http.Get("http://127.0.0.1:8080"); err != nil {
        log.Fatal(err)
    }
    log.Printf("second request done, %d connection", len(connections))
    if len(connections) != 2 {
        log.Fatalf("Why %d, not 2?", len(connections))
    }
}

ist

2020/07/24 22:10:19 first request done, 1 connection
2020/07/24 22:10:19 second request done, 1 connection
2020/07/24 22:10:19 Why 1, not 2?
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.