connection may be closed unexpectedly when Subscribe function called
happygraypig opened this issue · comments
Subscribe sends data first and then calls Flush. The underlying connection will be closed when the function Flush
of the bufio.Writer
return error.
The io.ErrShortWrite
is not exactly an error, it just means part of data was written to tcp buffer, it can succeed after retry.
whether should the Flush function be called many times when Flush returns io.ErrShortWrite
?
// **code from redigo**
// Subscribe subscribes the connection to the specified channels.
func (c PubSubConn) Subscribe(channel ...interface{}) error {
if err := c.Conn.Send("SUBSCRIBE", channel...); err != nil {
return err
}
return c.Conn.Flush()
}
func (c *conn) Flush() error {
if c.writeTimeout != 0 {
if err := c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)); err != nil {
return c.fatal(err)
}
}
if err := c.bw.Flush(); err != nil {
return c.fatal(err)
}
return nil
}
func (c *conn) fatal(err error) error {
c.mu.Lock()
if c.err == nil {
c.err = err
// Close connection to force errors on subsequent calls and to unblock
// other reader or writer.
c.conn.Close()
}
c.mu.Unlock()
return err
}
// **code from go sdk, bufio.go**
// Flush writes any buffered data to the underlying io.Writer.
func (b *Writer) Flush() error {
if b.err != nil {
return b.err
}
if b.n == 0 {
return nil
}
n, err := b.wr.Write(b.buf[0:b.n])
if n < b.n && err == nil {
err = io.ErrShortWrite
}
if err != nil {
if n > 0 && n < b.n {
copy(b.buf[0:b.n-n], b.buf[n:b.n])
}
b.n -= n
b.err = err
return err
}
b.n = 0
return nil
}
I can't say I've seen that error, do you have a case where this happens?
Actually looking at the bufio.Writer
Flush
method there really isn't anything this package can do to fix it due to the fact it checks b.err
before attempting the underlying flush, which means once you get io.ErrShortWrite
you will continue to get it.
Thank you for your explanation. Actually i just found it when i review code.
After I pass through all the related code, I think It will never happen.
The socket writer returns len(b),nil or n, err. It will never return n<len(b), nil
// Write implements io.Writer.
func (fd *FD) Write(p []byte) (int, error) {
if err := fd.writeLock(); err != nil {
return 0, err
}
defer fd.writeUnlock()
if err := fd.pd.prepareWrite(fd.isFile); err != nil {
return 0, err
}
var nn int
for {
max := len(p)
if fd.IsStream && max-nn > maxRW {
max = nn + maxRW
}
n, err := ignoringEINTR(func() (int, error) { return syscall.Write(fd.Sysfd, p[nn:max]) })
if n > 0 {
nn += n
}
if nn == len(p) {
return nn, err
}
if err == syscall.EAGAIN && fd.pd.pollable() {
if err = fd.pd.waitWrite(fd.isFile); err == nil {
continue
}
}
if err != nil {
return nn, err
}
if n == 0 {
return nn, io.ErrUnexpectedEOF
}
}
}
Thank you again!
I tried setting the buffer to 1Byte, but no io.ErrShortWrite
error occurred
d, err := net.Dial("tcp", "127.0.0.1:6379")
if err != nil {
panic(err)
}
if tc, ok := d.(*net.TCPConn); ok {
tc.SetWriteBuffer(1)
tc.SetReadBuffer(1)
}
conn := redis.NewConn(d, 0, 0)
//r.Do("GET", "Sown", "Sown")
conn.Send("SET", "example", "sownsownsownsownsownsownsownsownsownsownsownsown...")
err = conn.Flush()
fmt.Println(err)
if err != nil {
panic(err)
}
The reason is that the internal/poll/fd_unix.go:Write
method will always write in a loop
func (fd *FD) Write(p []byte) (int, error) {
for {
...
n, err := ignoringEINTRIO(syscall.Write, fd.Sysfd, p[nn:max])
if n > 0 {
nn += n
}
if nn == len(p) {
return nn, err
}
...
}