commit f8c69a179645d6b19ee60c05c3ab4e6725a49ccc from: Noel Cower date: Thu Jul 21 00:12:06 2016 UTC Fix race-y channel close/error assignment Data race occurred when assigning to err and then subsequently reading it from anywhere else. Moving the channel close down could potentially help, but far easier to just gate both with a sync.Once since this isn't a code path that seems like it needs to be super-performant. Changes cause CloseWithError to be call-able once, ensuring that there's no case where err is written to while being read (as would happen when closing and reading c.err from another goroutine the moment c.closed is closed). Signed-off-by: Noel Cower commit - 87c6cf4550e8bf9dac847f1c6437e453aadef7c8 commit + f8c69a179645d6b19ee60c05c3ab4e6725a49ccc blob - 80b1e9479bb7f916e95e8c9e0734ed4a889ac404 blob + 2c5ec955994e20ddbba22d86b7c3b9c475d23344 --- server.go +++ server.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "net" + "sync" "time" "golang.org/x/net/context" @@ -53,8 +54,10 @@ type conn struct { session Session ch Channel handler Handler - closed chan struct{} - err error // terminal error for the conn + + once sync.Once + closed chan struct{} + err error // terminal error for the conn } // activeRequest includes information about the active request. @@ -233,17 +236,14 @@ func (c *conn) Close() error { } func (c *conn) CloseWithError(err error) error { - select { - case <-c.closed: - return c.err - default: - close(c.closed) + c.once.Do(func() { if err == nil { - c.err = err - } else { - c.err = ErrClosed + err = ErrClosed } - return c.err - } + c.err = err + close(c.closed) + }) + + return c.err }