hashicorp / yamux

Golang connection multiplexing library
Mozilla Public License 2.0
2.24k stars 236 forks source link

let ErrTimeout implements net.Error #91

Closed fatedier closed 3 years ago

fatedier commented 3 years ago

Fix #90

Golang standard http server package will read from connections in background to detect if it's alive. It set read deadline on connection and detect if the returned error is timeout error which implements net.Error.

If not, it will call cr.handleReadError(err) and then call cr.conn.cancelCtx(), so server request.Context() will be canceled too.

func (cr *connReader) backgroundRead() {
    n, err := cr.conn.rwc.Read(cr.byteBuf[:])
    cr.lock()
    if n == 1 {
        cr.hasByte = true
        // We were past the end of the previous request's body already
        // (since we wouldn't be in a background read otherwise), so
        // this is a pipelined HTTP request. Prior to Go 1.11 we used to
        // send on the CloseNotify channel and cancel the context here,
        // but the behavior was documented as only "may", and we only
        // did that because that's how CloseNotify accidentally behaved
        // in very early Go releases prior to context support. Once we
        // added context support, people used a Handler's
        // Request.Context() and passed it along. Having that context
        // cancel on pipelined HTTP requests caused problems.
        // Fortunately, almost nothing uses HTTP/1.x pipelining.
        // Unfortunately, apt-get does, or sometimes does.
        // New Go 1.11 behavior: don't fire CloseNotify or cancel
        // contexts on pipelined requests. Shouldn't affect people, but
        // fixes cases like Issue 23921. This does mean that a client
        // closing their TCP connection after sending a pipelined
        // request won't cancel the context, but we'll catch that on any
        // write failure (in checkConnErrorWriter.Write).
        // If the server never writes, yes, there are still contrived
        // server & client behaviors where this fails to ever cancel the
        // context, but that's kinda why HTTP/1.x pipelining died
        // anyway.
    }
    if ne, ok := err.(net.Error); ok && cr.aborted && ne.Timeout() {
        // Ignore this error. It's the expected error from
        // another goroutine calling abortPendingRead.
    } else if err != nil {
        cr.handleReadError(err)
    }
    cr.aborted = false
    cr.inRead = false
    cr.unlock()
    cr.cond.Broadcast()
}

We'd better let ErrTimeout defined in consts.go to satisfy net.Error interface, so everything will be ok.

// An Error represents a network error.
type Error interface {
    error
    Timeout() bool   // Is the error a timeout?
    Temporary() bool // Is the error temporary?
}

cc @mitchellh @dadgar

hashicorp-cla commented 3 years ago

CLA assistant check
All committers have signed the CLA.

fatedier commented 3 years ago

This makes sense to me. Can we add a test that has a timeout and assert that the returned error meets the network Error interface and marks it as a timeout. In the comment of that test can you describe the reason we need this.

OK. I will add a test.

dadgar commented 3 years ago

Thanks for the contribution @fatedier