Co może się stać, jeśli nie zamknę odpowiedzi.

102

W Go mam kilka odpowiedzi http i czasami zapominam zadzwonić:

resp.Body.Close()

Co się dzieje w tym przypadku? czy nastąpi wyciek pamięci? Czy jest też bezpieczne wprowadzenie defer resp.Body.Close()natychmiast po otrzymaniu obiektu odpowiedzi?

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

Co się stanie, jeśli wystąpi błąd, respczy może resp.Bodybyć zerowy?

Daniel Robinson
źródło
Dobrze jest wstawić defer lub Body.Close () po err! = Nil, gdy występuje return, ponieważ gdy błąd nie jest zerowy, to już jest zamykany. Z drugiej strony treść musi zostać jawnie zamknięta, gdy żądanie się powiedzie.
Vasantha Ganesh

Odpowiedzi:

114

Co się dzieje w tym przypadku? czy nastąpi wyciek pamięci?

To wyciek zasobów. Połączenie nie zostanie ponownie użyte i może pozostać otwarte, w takim przypadku deskryptor pliku nie zostanie zwolniony.

Czy jest również bezpieczne wprowadzenie defer lub Body.Close () natychmiast po uzyskaniu obiektu odpowiedzi?

Nie, postępuj zgodnie z przykładem podanym w dokumentacji i zamknij go natychmiast po sprawdzeniu błędu.

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

Z http.Clientdokumentacji:

Jeśli zwrócony błąd wynosi zero, odpowiedź będzie zawierać niezerową treść, którą użytkownik powinien zamknąć. Jeśli treść nie jest jednocześnie odczytywana do EOF i zamykana, bazowy RoundTripper klienta (zazwyczaj Transport) może nie być w stanie ponownie użyć trwałego połączenia TCP z serwerem dla kolejnego żądania „utrzymywania aktywności”.

JimB
źródło
5
Zgodnie z tym linkiem nadal istnieje możliwość wycieku połączenia z Twoim kodem. Istnieją przypadki, w których odpowiedź jest różna od zera, a błąd jest różny od zera.
mmcdole
14
@mmcdole: Ten post jest po prostu błędny i nie ma gwarancji, że nie wpadnie w panikę, ponieważ każda odpowiedź zwracana na błąd nie ma zdefiniowanego stanu. Jeśli treść nie jest zamknięta z powodu błędu, jest to błąd i należy go zgłosić. Powinieneś zapoznać się z oficjalną dokumentacją klienta , która stwierdza: „W przypadku błędu każda odpowiedź może zostać zignorowana”, a nie losowy post na blogu.
JimB,
2
@ del-boy: Jeśli spodziewasz się, że klient będzie wysyłać więcej żądań, powinieneś spróbować odczytać treść, aby można było ponownie wykorzystać połączenie. Jeśli nie potrzebujesz połączenia, nie przejmuj się czytaniem treści. Jeśli czytasz treść, zawiń ją io.LimitReader. Zwykle używam dość małego limitu, ponieważ szybsze jest nawiązanie nowego połączenia, jeśli żądanie jest zbyt duże.
JimB,
1
Warto zwrócić uwagę, że _, err := client.Do(req)spowoduje to również pozostawienie otwartego deskryptora pliku. Więc nawet jeśli nie obchodzi nas, jaka jest odpowiedź, nadal konieczne jest przypisanie jej do zmiennej i zamknięcie ciała.
j boschiero
1
Dla wszystkich zainteresowanych pełna dokumentacja (podkreślenie dodane): „W przypadku błędu każda odpowiedź może zostać zignorowana. Niezerowa odpowiedź z błędem różnym od zera występuje tylko wtedy, gdy CheckRedirect nie powiedzie się, a nawet wtedy zwrócona odpowiedź.Body jest już Zamknięte."
nishanthshanmugham
15

Jeśli Response.Bodynie zostanie zamknięte Close()metodą, wówczas zasoby powiązane z fd nie zostaną zwolnione. To wyciek zasobów.

Zamknięcie Response.Body

Ze źródła odpowiedzi :

Obowiązkiem dzwoniącego jest zamknięcie Ciała.

Nie ma więc finalizatorów związanych z obiektem i musi być jawnie zamknięty.

Obsługa błędów i odroczone czyszczenie

W przypadku błędu każdą odpowiedź można zignorować. Niezerowa odpowiedź z błędem różnym od zera występuje tylko wtedy, gdy CheckRedirect nie powiedzie się, a nawet wtedy zwrócona odpowiedź Response.Body jest już zamknięta.

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
I159
źródło
4
Należy pamiętać, że powinny one powrócić w ramach warunku obsługi błędu. Spowoduje to panikę, jeśli użytkownik nie powróci w obsłudze błędów.
jabłoń
4

Początkowo deskryptor nigdy się nie zamyka, jak wspomniano powyżej.

Co więcej, golang buforuje połączenie (używając persistConnstruct to wrap) do ponownego użycia, jeśli DisableKeepAlivesjest fałszywe.

W golang po użyciu client.Dometody, go uruchomi readLoopmetodę goroutine jako jeden z kroków.

Więc w golang http transport.go, a pconn(persistConn struct)nie zostanie wstawiony do idleConnkanału, dopóki req nie zostanie anulowany w readLoopmetodzie, a także ta goroutine ( readLoopmetoda) zostanie zablokowana do czasu anulowania żądania .

Oto kod, który to pokazuje.

Jeśli chcesz wiedzieć więcej, musisz zapoznać się z readLoopmetodą.

zatrix
źródło
1

Zobacz https://golang.org/src/net/http/client.go
„Gdy błąd jest równy zero, resp zawsze zawiera wartość inną niż zero lub Body.”

ale nie mówią, kiedy err! = nil, resp zawsze jest nil.
Dalej mówią: „Jeśli odpowiednie ciało nie jest zamknięte, bazowy RoundTripper klienta (zazwyczaj Transport) może nie być w stanie ponownie użyć trwałego połączenia TCP z serwerem dla kolejnego żądania„ utrzymywania aktywności ”.

Więc zazwyczaj rozwiązałem problem w następujący sposób:

client := http.DefaultClient
resp, err := client.Do(req)
if resp != nil {
   defer resp.Body.Close()
}
if err != nil {
    return nil, err 
}
candita
źródło
3
Jest to niepoprawne i nie ma gwarancji, że odpowiednio ciało jest bez wartości w przypadku wystąpienia błędu.
JimB
1
Dzięki @JimB. Sformułowanie w dokumentach to „W przypadku błędu każda odpowiedź może zostać zignorowana”. Dokładniej byłoby powiedzieć: „W przypadku błędu treść odpowiedzi jest zawsze zamknięta”.
candita
1
Nie, ponieważ zwykle nie ma treści odpowiedzi do zamknięcia. Jeśli będziesz kontynuować czytanie tego akapitu w dokumentacji - „Niezerowa odpowiedź z niezerowym błędem występuje tylko wtedy, gdy CheckRedirect nie powiedzie się, a nawet wtedy zwrócona odpowiedź Response.Body jest już zamknięta.”
JimB,