Apa yang bisa terjadi jika saya tidak menutup respons. Tubuh?

98

Di Go, saya mendapat beberapa tanggapan http dan terkadang saya lupa menelepon:

resp.Body.Close()

Apa yang terjadi dalam kasus ini? apakah akan ada kebocoran memori? Juga apakah aman untuk dimasukkan defer resp.Body.Close()segera setelah mendapatkan objek respon?

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

Bagaimana jika ada kesalahan, bisa respatau resp.Bodynihil?

Daniel Robinson
sumber
Tidak apa-apa untuk meletakkan defer resp.Body.Close () setelah err! = Nil ketika return ada karena, ketika err bukan nihil, itu sudah ditutup. Di sisi lain, body harus ditutup secara eksplisit saat permintaan berhasil.
Vasantha Ganesh K

Jawaban:

110

Apa yang terjadi dalam kasus ini? apakah akan ada kebocoran memori?

Ini kebocoran sumber daya. Sambungan tidak akan digunakan kembali, dan dapat tetap terbuka dalam hal ini deskriptor file tidak akan dibebaskan.

Apakah juga aman untuk segera memasukkan defer resp.Body.Close () setelah mendapatkan objek respons?

Tidak, ikuti contoh yang diberikan dalam dokumentasi dan tutup segera setelah memeriksa kesalahan.

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

Dari http.Clientdokumentasi:

Jika error yang dikembalikan nihil, Respons akan berisi Body non-nil yang diharapkan untuk ditutup oleh pengguna. Jika Body tidak dibaca untuk EOF dan ditutup, RoundTripper yang mendasari Klien (biasanya Transport) mungkin tidak dapat menggunakan kembali koneksi TCP persisten ke server untuk permintaan "keep-hidup" berikutnya.

JimB
sumber
4
Menurut tautan ini masih mungkin terjadi kebocoran koneksi dengan kode Anda. Ada beberapa contoh di mana responsnya adalah non-nil dan kesalahannya adalah non-nil.
mmcdole
13
@mmcdole: Posting itu salah, dan tidak ada jaminan bahwa ia tidak akan panik, karena respons apa pun yang dikembalikan pada kesalahan tidak memiliki status yang ditentukan. Jika Body tidak ditutup karena kesalahan, maka itu bug dan perlu dilaporkan. Anda harus membuka dokumentasi klien resmi , yang menyatakan "Jika ada kesalahan, setiap Tanggapan dapat diabaikan", bukan posting blog acak.
JimB
2
@ del-boy: Jika Anda mengharapkan klien tersebut untuk membuat lebih banyak permintaan, maka Anda harus mencoba untuk membaca isi sehingga koneksi dapat digunakan kembali. Jika Anda tidak membutuhkan koneksi, maka jangan repot-repot membaca isi. Jika Anda membaca tubuh, bungkus dengan io.LimitReader. Saya biasanya menggunakan batas yang cukup kecil, karena lebih cepat membuat sambungan baru jika permintaan terlalu besar.
JimB
1
Penting untuk diperhatikan bahwa melakukan _, err := client.Do(req)juga akan membuat deskriptor file tetap terbuka. Jadi bahkan jika seseorang tidak peduli apa responnya, tetap perlu untuk menugaskannya ke variabel dan menutup tubuh.
j boschiero
1
Bagi siapa pun yang tertarik, dokumentasi lengkapnya adalah (penekanan ditambahkan): "Pada kesalahan, Respon apapun dapat diabaikan. Respon non-nil dengan kesalahan non-nil hanya terjadi ketika CheckRedirect gagal, dan bahkan kemudian Respon yang dikembalikan sudah Tutup."
nishanthshanmugham
15

Jika Response.Bodytidak akan ditutup dengan Close()metode daripada sumber daya yang terkait dengan fd tidak akan dibebaskan. Ini adalah kebocoran sumber daya.

Penutupan Response.Body

Dari sumber tanggapan :

Merupakan tanggung jawab pemanggil untuk menutup Tubuh.

Jadi tidak ada finalisator yang terikat ke objek dan harus ditutup secara eksplisit.

Penanganan kesalahan dan pembersihan tertunda

Jika terjadi kesalahan, setiap Respons dapat diabaikan. Respons non-nil dengan kesalahan non-nil hanya terjadi saat CheckRedirect gagal, dan bahkan Respons yang dikembalikan. Tubuh sudah ditutup.

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
sumber
4
Anda harus mencatat bahwa mereka harus kembali dalam kondisi penanganan kesalahan Anda. Ini akan menyebabkan kepanikan jika pengguna tidak kembali dalam penanganan kesalahan mereka.
kayu apel
3

Awalnya deskriptor tidak pernah menutup, seperti hal-hal yang disebutkan di atas.

Dan terlebih lagi, golang akan meng-cache koneksi (menggunakan persistConnstruct untuk membungkus) untuk digunakan kembali, jika DisableKeepAlivessalah.

Pada golang after use client.Domethod, go akan menjalankan readLoopmetode goroutine sebagai salah satu langkahnya.

Jadi pada golang http transport.go, a pconn(persistConn struct)tidak akan dimasukkan ke idleConnchannel sampai req dibatalkan dalam readLoopmetode tersebut, dan juga goroutine ( readLoopmetode) ini akan diblokir sampai req tersebut dibatalkan.

Ini kode yang menunjukkannya.

Jika Anda ingin tahu lebih banyak, Anda perlu melihat readLoopcaranya.

zatrix
sumber
1

Lihat https://golang.org/src/net/http/client.go
"Jika err nihil, resp selalu berisi resp.Body non-nil."

tetapi mereka tidak mengatakan bila err! = nil, resp selalu nihil. Mereka melanjutkan dengan mengatakan:
"Jika resp.Body tidak ditutup, RoundTripper yang mendasari Klien (biasanya Transport) mungkin tidak dapat menggunakan kembali koneksi TCP persisten ke server untuk permintaan" tetap hidup "berikutnya."

Jadi saya biasanya memecahkan masalah seperti ini:

client := http.DefaultClient
resp, err := client.Do(req)
if resp != nil {
   defer resp.Body.Close()
}
if err != nil {
    return nil, err 
}
candita
sumber
3
Ini tidak benar, dan tidak ada jaminan bahwa resp. Body adalah nit nil ketika ada kesalahan.
JimB
1
Terima kasih @JimB. Kata-kata dalam dokumen tersebut adalah "Jika salah, setiap Tanggapan dapat diabaikan." Akan lebih akurat untuk mengatakan "Jika salah, respons Tubuh selalu tertutup."
candita
1
Tidak, karena biasanya tidak ada badan respons yang akan ditutup. Jika Anda terus membaca paragraf tersebut di dokumen - "Respons non-nil dengan kesalahan non-nil hanya terjadi saat CheckRedirect gagal, dan bahkan Response.Body yang dikembalikan sudah ditutup."
JimB