Closed
Description
In the fix for the deadlock introduced in 1.8.5, we have the following:
err = c.writeFrameMu.lock(ctx)
if err != nil {
return 0, err
}
defer c.writeFrameMu.unlock()
// If the state says a close has already been written, we wait until
// the connection is closed and return that error.
//
// However, if the frame being written is a close, that means its the close from
// the state being set so we let it go through.
c.closeMu.Lock()
wroteClose := c.wroteClose
c.closeMu.Unlock()
if wroteClose && opcode != opClose {
select {
case <-ctx.Done():
return 0, ctx.Err()
case <-c.closed:
return 0, c.closeErr
}
}
...
If there is a (*Conn).Write
that passes the if
condition, then it waits for at least one of two channels to close, while still holding the writeFrameMu
lock. Since c.wroteClose
is set before (*Conn).CloseRead
gets to call (*Conn).writeFrame
(see (*Conn).writeClose
), it is possible that (*Conn).CloseRead
calls (*Conn).writeFrame
after (*Conn).Write
gets stuck, so (*Conn).CloseRead
will get stuck waiting to get writeFrameMu
, and no progress can be made.
One option would be to unlock writeFrameMu
immediately after passing the if
condition. defer c.writeFrameMu.unlock()
would need to be removed since the mutex implementation doesn't allow for unlocking twice.