gomodule / redigo

Go client for Redis

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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!

commented

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
		}
		...
	}