¿Qué podría pasar si no cierro la respuesta. Cuerpo?

98

En Go, tengo algunas respuestas http y a veces me olvido de llamar:

resp.Body.Close()

¿Qué pasa en este caso? habrá una fuga de memoria? ¿También es seguro colocarlo defer resp.Body.Close()inmediatamente después de obtener el objeto de respuesta?

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

¿Qué pasa si hay un error, podía respo resp.Bodysea nula?

Daniel Robinson
fuente
Está bien poner defer resp.Body.Close () después de err! = Nil cuando return está presente porque, cuando err no es nil, ya viene cerrado. Por otro lado, el cuerpo debe cerrarse explícitamente cuando la solicitud tiene éxito.
Vasantha Ganesh K

Respuestas:

110

¿Qué pasa en este caso? ¿Habrá una fuga de memoria?

Es una fuga de recursos. La conexión no se volverá a utilizar y puede permanecer abierta, en cuyo caso el descriptor de archivo no se liberará.

Además, ¿es seguro poner en aplazamiento resp. Body.Close () inmediatamente después de obtener el objeto de respuesta?

No, siga el ejemplo proporcionado en la documentación y ciérrelo inmediatamente después de verificar el error.

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

De la http.Clientdocumentación:

Si el error devuelto es nulo, la Respuesta contendrá un Cuerpo no nulo que se espera que el usuario cierre. Si el cuerpo no se lee en EOF y no se cierra, es posible que el RoundTripper subyacente del cliente (normalmente Transporte) no pueda reutilizar una conexión TCP persistente con el servidor para una solicitud subsiguiente de "mantener vivo".

JimB
fuente
4
Según este enlace , todavía es posible filtrar la conexión con su código. Hay algunos casos en los que la respuesta no es nula y el error no es nulo.
mmcdole
13
@mmcdole: Esa publicación es simplemente incorrecta y no hay garantía de que no entre en pánico, ya que cualquier respuesta que se devuelva en un error no tiene un estado definido. Si un cuerpo no se cierra debido a un error, entonces es un error y debe informarse. Debe consultar la documentación oficial del cliente , que dice "En caso de error, se puede ignorar cualquier respuesta", en lugar de una publicación de blog aleatoria.
JimB
2
@ del-boy: Si espera que el cliente haga más solicitudes, entonces debería intentar leer el cuerpo para que la conexión se pueda reutilizar. Si no necesita la conexión, no se moleste en leer el cuerpo. Si lee el cuerpo, envuélvalo con io.LimitReader. Por lo general, uso un límite bastante pequeño, ya que es más rápido establecer una nueva conexión si la solicitud es demasiado grande.
JimB
1
Vale la pena señalar que hacerlo _, err := client.Do(req)también da como resultado que el descriptor de archivo permanezca abierto. Entonces, incluso si a uno no le importa cuál es la respuesta, aún es necesario asignarla a una variable y cerrar el cuerpo.
j boschiero
1
Para cualquier persona interesada, la documentación completa es (énfasis agregado): "En caso de error, se puede ignorar cualquier Respuesta. Una Respuesta no nula con un error no nulo solo ocurre cuando CheckRedirect falla, e incluso entonces la Respuesta devuelta. cerrado."
nishanthshanmugham
15

Si Response.Bodyno se cerrará con el Close()método, los recursos asociados con un fd no se liberarán. Esta es una fuga de recursos.

Clausura Response.Body

De la fuente de respuesta :

Es responsabilidad de la persona que llama cerrar Body.

Por lo tanto, no hay finalizadores vinculados al objeto y debe cerrarse explícitamente.

Manejo de errores y limpiezas diferidas

En caso de error, se puede ignorar cualquier respuesta. Una respuesta no nula con un error no nulo solo ocurre cuando CheckRedirect falla, e incluso entonces el Response.Body devuelto ya está cerrado.

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
fuente
4
Debe tener en cuenta que deben regresar dentro de su condición de manejo de errores. Esto va a causar pánico si el usuario no regresa en su manejo de errores.
Applewood
3

Al principio, el descriptor nunca se cierra, como se mencionó anteriormente.

Y lo que es más, golang almacenará en caché la conexión (usando persistConnstruct para envolver) para reutilizarla, si DisableKeepAliveses falso.

En golang después del client.Dométodo de uso , go ejecutará el readLoopmétodo goroutine como uno de los pasos.

Entonces, en golang http transport.go, pconn(persistConn struct)no se colocará en el idleConncanal hasta que se cancele la solicitud en el readLoopmétodo, y también esta goroutine ( readLoopmétodo) se bloqueará hasta que se cancele la solicitud.

Aquí está el código que lo muestra.

Si quieres saber más, necesitas ver el readLoopmétodo.

zatrix
fuente
1

Consulte https://golang.org/src/net/http/client.go
"Cuando err es nil, resp siempre contiene un resp. No nil".

pero no dicen cuando err! = nil, resp siempre es nil.
Continúan diciendo: "Si resp.Body no está cerrado, es posible que el RoundTripper subyacente del Cliente (normalmente Transporte) no pueda reutilizar una conexión TCP persistente con el servidor para una solicitud subsiguiente de" mantener vivo ".

Entonces, normalmente he resuelto el problema de esta manera:

client := http.DefaultClient
resp, err := client.Do(req)
if resp != nil {
   defer resp.Body.Close()
}
if err != nil {
    return nil, err 
}
candita
fuente
3
Esto es incorrecto y no hay garantía de que el cuerpo resp. Sea nulo cuando hay un error.
JimB
1
Gracias @JimB. La redacción de los documentos es "En caso de error, se puede ignorar cualquier respuesta". Sería más exacto decir "En caso de error, el cuerpo de respuesta siempre está cerrado".
candita
1
No, porque normalmente no hay un cuerpo de respuesta para cerrar. Si continúa leyendo ese párrafo en los documentos: "Una respuesta no nula con un error no nulo solo se produce cuando CheckRedirect falla, e incluso entonces el Response.Body devuelto ya está cerrado".
JimB