จะเกิดอะไรขึ้นถ้าฉันไม่ตอบกลับ Body?


98

ใน Go ฉันได้รับคำตอบจาก http และบางครั้งฉันลืมโทร:

resp.Body.Close()

จะเกิดอะไรขึ้นในกรณีนี้? จะมีการรั่วไหลของหน่วยความจำหรือไม่ นอกจากนี้ยังปลอดภัยที่จะใส่defer resp.Body.Close()ทันทีหลังจากได้รับวัตถุตอบสนองหรือไม่?

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

จะเกิดอะไรขึ้นหากมีข้อผิดพลาดอาจเป็นrespหรือresp.Bodyไม่มี


ไม่เป็นไรที่จะใส่ defer resp.Body.Close () หลังจาก err! = nil เมื่อมีการส่งคืนเนื่องจากเมื่อข้อผิดพลาดไม่เป็นศูนย์ก็จะปิดไปแล้ว ในทางกลับกันร่างกายจำเป็นต้องปิดอย่างชัดเจนเมื่อคำขอสำเร็จ
Vasantha Ganesh K

คำตอบ:


110

จะเกิดอะไรขึ้นในกรณีนี้? จะมีการรั่วไหลของหน่วยความจำหรือไม่

มันคือการรั่วไหลของทรัพยากร การเชื่อมต่อจะไม่ถูกใช้ซ้ำและยังคงเปิดอยู่ได้ในกรณีนี้ตัวอธิบายไฟล์จะไม่ถูกปล่อยให้เป็นอิสระ

นอกจากนี้ยังปลอดภัยที่จะใส่ defer resp.Body.Close () ทันทีหลังจากได้รับวัตถุตอบสนอง?

ไม่ทำตามตัวอย่างที่ให้ไว้ในเอกสารและปิดทันทีหลังจากตรวจสอบข้อผิดพลาด

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

จากhttp.Clientเอกสารประกอบ:

หากข้อผิดพลาดที่ส่งคืนเป็นศูนย์การตอบกลับจะมีเนื้อหาที่ไม่ใช่ศูนย์ซึ่งคาดว่าผู้ใช้จะปิด ถ้า Body ไม่ได้อ่านทั้ง EOF และปิด RoundTripper (โดยทั่วไปคือ Transport) ของไคลเอ็นต์อาจไม่สามารถใช้การเชื่อมต่อ TCP ต่อเนื่องกับเซิร์ฟเวอร์ซ้ำสำหรับคำขอ "keep-alive" ในภายหลัง


4
ตามลิงค์นี้ยังคงเป็นไปได้ที่จะรั่วไหลการเชื่อมต่อกับรหัสของคุณ มีบางกรณีที่การตอบสนองไม่เป็นศูนย์และข้อผิดพลาดไม่ใช่ศูนย์
mmcdole

13
@mmcdole: โพสต์นั้นไม่ถูกต้องและไม่มีการรับประกันว่าจะไม่ตื่นตระหนกเนื่องจากการตอบสนองใด ๆ ที่ส่งกลับเมื่อเกิดข้อผิดพลาดไม่มีสถานะที่กำหนด หากร่างกายไม่ได้ปิดเนื่องจากเกิดข้อผิดพลาดแสดงว่าเป็นข้อบกพร่องและจำเป็นต้องรายงาน คุณควรอ่านเอกสารอย่างเป็นทางการของลูกค้าซึ่งระบุว่า "เมื่อเกิดข้อผิดพลาดสามารถละเว้นการตอบกลับใด ๆ ได้" แทนที่จะเป็นบล็อกโพสต์แบบสุ่ม
JimB

2
@ del-boy: หากคุณคาดหวังว่าลูกค้าจะส่งคำขอเพิ่มเติมคุณควรพยายามอ่านเนื้อหาเพื่อให้สามารถใช้การเชื่อมต่อซ้ำได้ หากคุณไม่ต้องการการเชื่อมต่อก็ไม่ต้องกังวลกับการอ่านเนื้อหา io.LimitReaderถ้าคุณอ่านร่างกายห่อมันด้วย ฉันมักจะใช้ขีด จำกัด ที่ค่อนข้างเล็กเนื่องจากการเชื่อมต่อใหม่จะเร็วกว่าหากคำขอใหญ่เกินไป
JimB

1
ควรชี้ให้เห็นว่าการทำเช่น_, err := client.Do(req)นี้ส่งผลให้ตัวอธิบายไฟล์ยังคงเปิดอยู่ ดังนั้นแม้ว่าใครจะไม่สนใจว่าการตอบสนองคืออะไร แต่ก็ยังจำเป็นต้องกำหนดให้กับตัวแปรและปิดเนื้อหา
j boschiero

1
สำหรับทุกคนที่สนใจเอกสารฉบับเต็มคือ (เพิ่มการเน้น): "เมื่อเกิดข้อผิดพลาดการตอบกลับใด ๆ สามารถถูกละเว้นได้การตอบสนองที่ไม่มีศูนย์ที่มีข้อผิดพลาดที่ไม่ใช่ศูนย์จะเกิดขึ้นเมื่อ CheckRedirect ล้มเหลวเท่านั้นและจากนั้น Response.Body ที่ส่งคืนแล้ว ปิด."
nishanthshanmugham

15

หากResponse.Bodyไม่ถูกปิดด้วยClose()วิธีการกว่าทรัพยากรที่เชื่อมโยงกับ fd จะไม่ถูกปลดปล่อย นี่คือการรั่วไหลของทรัพยากร

กำลังปิด Response.Body

จากแหล่งที่มาของคำตอบ :

เป็นความรับผิดชอบของผู้โทรในการปิด Body

ดังนั้นจึงไม่มีขั้นสุดท้ายที่ผูกไว้กับวัตถุและต้องปิดอย่างชัดเจน

เกิดข้อผิดพลาดในการจัดการและการล้างข้อมูลที่รอการตัดบัญชี

เมื่อเกิดข้อผิดพลาดสามารถละเว้นการตอบสนองใด ๆ การตอบสนองที่ไม่เป็นศูนย์ที่มีข้อผิดพลาดที่ไม่ใช่ศูนย์เกิดขึ้นเฉพาะเมื่อ CheckRedirect ล้มเหลวและแม้กระทั่งการตอบกลับที่ส่งคืน Body ถูกปิดไปแล้ว

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
คุณควรทราบว่าควรส่งคืนภายในเงื่อนไขการจัดการข้อผิดพลาดของคุณ สิ่งนี้จะทำให้เกิดความตื่นตระหนกหากผู้ใช้ไม่กลับมาจัดการข้อผิดพลาด
applewood

3

ในตอนแรกตัวอธิบายไม่เคยปิดดังที่กล่าวไว้ข้างต้น

และยิ่งไปกว่านั้น golang จะแคชการเชื่อมต่อ (โดยใช้โครงสร้างpersistConnเพื่อตัด) เพื่อนำกลับมาใช้ใหม่หากDisableKeepAlivesเป็นเท็จ

ใน golang หลังจากใช้client.Doวิธี go จะเรียกใช้ goroutine readLoopmethod เป็นหนึ่งในขั้นตอน

ดังนั้นใน golang http transport.goที่pconn(persistConn struct)จะไม่ได้ใส่ลงไปในidleConnช่องจนกว่า req ยกเลิกในreadLoopวิธีการและยัง goroutine นี้ ( readLoopวิธีการ) จะถูกปิดกั้นจน req ยกเลิก

นี่คือรหัสที่แสดง

หากต้องการทราบข้อมูลเพิ่มเติมคุณต้องดูreadLoopวิธีการ


1

โปรดดูhttps://golang.org/src/net/http/client.go
"เมื่อ err เป็นศูนย์ resp จะมีการตอบสนองที่ไม่ใช่ศูนย์เสมอ"

แต่พวกเขาไม่ได้พูดเมื่อ err! = nil, resp always is nil พวกเขากล่าวต่อไปว่า:
"หาก resp.Body ไม่ถูกปิด RoundTripper (โดยทั่วไปคือ Transport) ของไคลเอ็นต์อาจไม่สามารถใช้การเชื่อมต่อ TCP ต่อเนื่องกับเซิร์ฟเวอร์ซ้ำสำหรับคำขอ" keep-alive "ในภายหลังได้

โดยทั่วไปฉันได้แก้ไขปัญหาดังนี้:

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

3
สิ่งนี้ไม่ถูกต้องและไม่มีการรับประกันว่าการตอบสนองร่างกายจะเป็นศูนย์เมื่อมีข้อผิดพลาด
JimB

1
ขอบคุณ @JimB. ข้อความในเอกสารคือ "เมื่อเกิดข้อผิดพลาดสามารถละเว้นการตอบกลับใด ๆ ได้" การพูดว่า "เมื่อเกิดข้อผิดพลาดเนื้อหาการตอบกลับจะปิดเสมอ"
candita

1
ไม่เพราะโดยปกติจะไม่มีการปิดเนื้อหาตอบสนอง หากคุณอ่านย่อหน้านั้นต่อไปในเอกสาร - "การตอบสนองที่ไม่เป็นศูนย์พร้อมข้อผิดพลาดที่ไม่ใช่ศูนย์จะเกิดขึ้นเฉพาะเมื่อ CheckRedirect ล้มเหลวและถึงแม้ว่าการตอบกลับที่ส่งคืน Body จะถูกปิดไปแล้ว"
JimB
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.