
php小编百草在解决编程问题时,发现了一个常见的goroutine泄漏情况。当我们尝试使用`ReadAll(response.Body)`时,返回的`response`对象是由`http`包的`client.Do(...)`方法返回的。然而,这种操作会导致goroutine泄漏的问题。为了避免这种情况的发生,我们需要采取一些措施来正确处理http响应。
该问题来自一个非常常见的场景,即在 http 方法调用后从响应正文中读取所有字节。
在 https://github.com/uber-go/goleak 的帮助下,我发现了一个有趣的 goroutine 泄漏。
为了演示该问题,请使用 go test 运行以下测试代码,这会导致 found 出现意外的 goroutine:
package main
import (
"io"
"net/http"
"testing"
"go.uber.org/goleak"
"gotest.tools/v3/assert"
)
func testleak(t *testing.t) {
defer goleak.verifynone(t)
request, err := http.newrequest(http.methodget, "https://google.com", nil)
assert.nilerror(t, err)
client := http.client{}
response, err := client.do(request)
assert.nilerror(t, err)
defer response.body.close()
_, err = io.readall(response.body)
assert.nilerror(t, err)
}
完整测试输出:
$ go test
--- FAIL: TestLeak (1.26s)
main_test.go:28: found unexpected goroutines:
[Goroutine 14 in state IO wait, with internal/poll.runtime_pollWait on top of the stack:
goroutine 14 [IO wait]:
internal/poll.runtime_pollWait(0x7f945c1e0398, 0x72)
/home/wxh/.local/go/src/runtime/netpoll.go:305 +0x89
internal/poll.(*pollDesc).wait(0xc000154280?, 0xc0000ca000?, 0x0)
/home/wxh/.local/go/src/internal/poll/fd_poll_runtime.go:84 +0x32
internal/poll.(*pollDesc).waitRead(...)
/home/wxh/.local/go/src/internal/poll/fd_poll_runtime.go:89
internal/poll.(*FD).Read(0xc000154280, {0xc0000ca000, 0x2000, 0x2000})
/home/wxh/.local/go/src/internal/poll/fd_unix.go:167 +0x25a
net.(*netFD).Read(0xc000154280, {0xc0000ca000?, 0xc00017edd8?, 0xc0004d5808?})
/home/wxh/.local/go/src/net/fd_posix.go:55 +0x29
net.(*conn).Read(0xc00009c000, {0xc0000ca000?, 0xc0000c68f0?, 0x2c?})
/home/wxh/.local/go/src/net/net.go:183 +0x45
crypto/tls.(*atLeastReader).Read(0xc0002d4138, {0xc0000ca000?, 0x0?, 0x29f6540f238f2947?})
/home/wxh/.local/go/src/crypto/tls/conn.go:787 +0x3d
bytes.(*Buffer).ReadFrom(0xc0000a0278, {0x7c3860, 0xc0002d4138})
/home/wxh/.local/go/src/bytes/buffer.go:202 +0x98
crypto/tls.(*Conn).readFromUntil(0xc0000a0000, {0x7c3b80?, 0xc00009c000}, 0xc0000a41c0?)
/home/wxh/.local/go/src/crypto/tls/conn.go:809 +0xe5
crypto/tls.(*Conn).readRecordOrCCS(0xc0000a0000, 0x0)
/home/wxh/.local/go/src/crypto/tls/conn.go:616 +0x116
crypto/tls.(*Conn).readRecord(...)
/home/wxh/.local/go/src/crypto/tls/conn.go:582
crypto/tls.(*Conn).Read(0xc0000a0000, {0xc0001cf000, 0x1000, 0x11?})
/home/wxh/.local/go/src/crypto/tls/conn.go:1287 +0x16f
bufio.(*Reader).Read(0xc0001af1a0, {0xc0001aad60, 0x9, 0xc0004d5d18?})
/home/wxh/.local/go/src/bufio/bufio.go:237 +0x1bb
io.ReadAtLeast({0x7c3760, 0xc0001af1a0}, {0xc0001aad60, 0x9, 0x9}, 0x9)
/home/wxh/.local/go/src/io/io.go:332 +0x9a
io.ReadFull(...)
/home/wxh/.local/go/src/io/io.go:351
net/http.http2readFrameHeader({0xc0001aad60?, 0x9?, 0xc0004d5df0?}, {0x7c3760?, 0xc0001af1a0?})
/home/wxh/.local/go/src/net/http/h2_bundle.go:1565 +0x6e
net/http.(*http2Framer).ReadFrame(0xc0001aad20)
/home/wxh/.local/go/src/net/http/h2_bundle.go:1829 +0x95
net/http.(*http2clientConnReadLoop).run(0xc0004d5f98)
/home/wxh/.local/go/src/net/http/h2_bundle.go:8875 +0x130
net/http.(*http2ClientConn).readLoop(0xc00009e180)
/home/wxh/.local/go/src/net/http/h2_bundle.go:8771 +0x6f
created by net/http.(*http2Transport).newClientConn
/home/wxh/.local/go/src/net/http/h2_bundle.go:7478 +0xaaa
Goroutine 37 in state IO wait, with internal/poll.runtime_pollWait on top of the stack:
goroutine 37 [IO wait]:
internal/poll.runtime_pollWait(0x7f945c1e02a8, 0x72)
/home/wxh/.local/go/src/runtime/netpoll.go:305 +0x89
internal/poll.(*pollDesc).wait(0xc0003ca000?, 0xc000202000?, 0x0)
/home/wxh/.local/go/src/internal/poll/fd_poll_runtime.go:84 +0x32
internal/poll.(*pollDesc).waitRead(...)
/home/wxh/.local/go/src/internal/poll/fd_poll_runtime.go:89
internal/poll.(*FD).Read(0xc0003ca000, {0xc000202000, 0x1300, 0x1300})
/home/wxh/.local/go/src/internal/poll/fd_unix.go:167 +0x25a
net.(*netFD).Read(0xc0003ca000, {0xc000202000?, 0xc0004e6798?, 0xc0004c0808?})
/home/wxh/.local/go/src/net/fd_posix.go:55 +0x29
net.(*conn).Read(0xc000014028, {0xc000202000?, 0xc000136630?, 0x2c?})
/home/wxh/.local/go/src/net/net.go:183 +0x45
crypto/tls.(*atLeastReader).Read(0xc000276288, {0xc000202000?, 0x0?, 0x150fbb8ccb566149?})
/home/wxh/.local/go/src/crypto/tls/conn.go:787 +0x3d
bytes.(*Buffer).ReadFrom(0xc0000a0978, {0x7c3860, 0xc000276288})
/home/wxh/.local/go/src/bytes/buffer.go:202 +0x98
crypto/tls.(*Conn).readFromUntil(0xc0000a0700, {0x7c3b80?, 0xc000014028}, 0xc0004cc2c0?)
/home/wxh/.local/go/src/crypto/tls/conn.go:809 +0xe5
crypto/tls.(*Conn).readRecordOrCCS(0xc0000a0700, 0x0)
/home/wxh/.local/go/src/crypto/tls/conn.go:616 +0x116
crypto/tls.(*Conn).readRecord(...)
/home/wxh/.local/go/src/crypto/tls/conn.go:582
crypto/tls.(*Conn).Read(0xc0000a0700, {0xc00023c000, 0x1000, 0x11?})
/home/wxh/.local/go/src/crypto/tls/conn.go:1287 +0x16f
bufio.(*Reader).Read(0xc00022a2a0, {0xc00020e2e0, 0x9, 0xc0004c0d18?})
/home/wxh/.local/go/src/bufio/bufio.go:237 +0x1bb
io.ReadAtLeast({0x7c3760, 0xc00022a2a0}, {0xc00020e2e0, 0x9, 0x9}, 0x9)
/home/wxh/.local/go/src/io/io.go:332 +0x9a
io.ReadFull(...)
/home/wxh/.local/go/src/io/io.go:351
net/http.http2readFrameHeader({0xc00020e2e0?, 0x9?, 0xc0004c0df0?}, {0x7c3760?, 0xc00022a2a0?})
/home/wxh/.local/go/src/net/http/h2_bundle.go:1565 +0x6e
net/http.(*http2Framer).ReadFrame(0xc00020e2a0)
/home/wxh/.local/go/src/net/http/h2_bundle.go:1829 +0x95
net/http.(*http2clientConnReadLoop).run(0xc0004c0f98)
/home/wxh/.local/go/src/net/http/h2_bundle.go:8875 +0x130
net/http.(*http2ClientConn).readLoop(0xc00019e300)
/home/wxh/.local/go/src/net/http/h2_bundle.go:8771 +0x6f
created by net/http.(*http2Transport).newClientConn
/home/wxh/.local/go/src/net/http/h2_bundle.go:7478 +0xaaa
]
FAIL
exit status 1
有人知道如何解决这个问题吗?
谢谢!
在 Go 中,http.Client 使用 Transport(http.RoundTripper 接口)。 http.Transport 是它的默认实现。默认的 http.Transfer 维护一个连接池,其中保存由于某种原因保持活动或空闲的连接。
基本上,您需要做的就是确保在调用goleak.Verify时池中没有连接*
这可以通过以下方式完成:
client := http.Client{Transport: &http.Transport{DisableKeepAlive: true}}
client.CloseIdleConnections() // 自 Go 1.12 起可用以上就是当尝试 `ReadAll(response.Body)` 时,goroutine 泄漏,其中 `response` 由 http `client.Do(...)` 返回的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号