ddliu / go-httpclient

Advanced HTTP client for golang
MIT License
465 stars 105 forks source link

对于defaultClient同时使用并发和非并发方式会导致panic #41

Closed oscarwin closed 4 years ago

oscarwin commented 4 years ago

在一个工程里如果 pkg A 使用了非并发的方式,而 pkg B 使用了并发的方式,会导致panic。前提是都使用 defaultClient。

panic 信息:

fatal error: sync: unlock of unlocked mutex

goroutine 69 [running]:
runtime.throw(0x158603d, 0x1e)
        /usr/local/go/src/runtime/panic.go:774 +0x72 fp=0xc0002cd978 sp=0xc0002cd948 pc=0x1030fb2
sync.throw(0x158603d, 0x1e)
        /usr/local/go/src/runtime/panic.go:760 +0x35 fp=0xc0002cd998 sp=0xc0002cd978 pc=0x1030f35
sync.(*Mutex).unlockSlow(0xc0000ba2a8, 0xffffffffffffffff)
        /usr/local/go/src/sync/mutex.go:196 +0xd6 fp=0xc0002cd9c0 sp=0xc0002cd998 pc=0x106e0b6
sync.(*Mutex).Unlock(...)
        /usr/local/go/src/sync/mutex.go:190
github.com/ddliu/go-httpclient.(*HttpClient).reset(0x19718c0)
        /Users/liuteng/go/pkg/mod/github.com/ddliu/go-httpclient@v0.6.5/httpclient.go:480 +0x86 fp=0xc0002cd9e0 sp=0xc0002cd9c0 pc=0x132acf6
github.com/ddliu/go-httpclient.(*HttpClient).Do(0x19718c0, 0x157b146, 0x3, 0xc0001b2000, 0x9b, 0xc000160480, 0x0, 0x0, 0xd, 0x1932640, ...)
        /Users/liuteng/go/pkg/mod/github.com/ddliu/go-httpclient@v0.6.5/httpclient.go:584 +0x1a0 fp=0xc0002cdb38 sp=0xc0002cd9e0 pc=0x132b510

panic 原因: withLock 为 true,非并发方式也会调用 Unlock 导致 panic。

// Reset the client state so that other requests can begin.
func (this *HttpClient) reset() {
    this.oneTimeOptions = nil
    this.oneTimeHeaders = nil
    this.oneTimeCookies = nil
    this.reuseTransport = true
    this.reuseJar = true

    // nil means the Begin has not been called, asume requests are not
    // concurrent.
    if this.withLock {
        this.lock.Unlock()
    }
}

即使在 Unlock 后将 withLock 置为 false,也不能解决问题,似乎只能将并发方式与非并发的 client 分开,或者每次都创建新的 client?

ddliu commented 4 years ago

以下测试用例会触发这个错误,已经尝试修复。但是真正高并发情况下这个错误还是可能出现。并发情况下还是建议先调用Begin()

func TestIssue41(t *testing.T) {
    c := NewHttpClient()
    c.Begin().Get("http://httpbin.org")
    c.Get("http://httpbin.org")
}
oscarwin commented 4 years ago

是的,对于同一个client不能混合使用两种方式,我现在是每个包里分别创建各自的 client 来避免这个问题。因为并不是所有的都需要并发,当然对于非并发的方式也可以采用先调用 Begin() 来解决这个问题。非并发其实可以看成并发为 1 的特殊场景。