Nutshell

Thoughts come and go, words stay eternal

27 Jul 2022

Abstract

  1. Two way to detect half closed connection
    1. common: read 1-byte with read deadline
    2. unix-like: subscribe epoll event syscall.EPOLLRDHUP | syscall.EPOLLHUP | syscall.EPOLLERR
  2. When using connection pool, it good to set correct connection timeout, base on RFC and less than recommened

Introduction

$common$ read 1-byte with deadline

depend on program language or platform implementation, some use read 0-byte

WARNING: you should only use this way when you ensure there's no unread value data

// Golang, read 0-byte, always return nil
// https://github.com/golang/go/issues/10940#issuecomment-245773886
func checkConn(conn net.Conn, timeout time.Duration) error {

	buf := make([]byte, 1)
	err := conn.SetReadDeadline(time.Now().Add(timeout))
	if err != nil {
		return err
	}

	n, err := conn.Read(buf)
	if err == io.EOF {
		return err
	}

	if n != 0 {
		return errors.New("connection has unread data, can't be reuse")
	}
	
	return nil
}

$linux$ subscribe epoll event

const watchEpollEvents uint32 = syscall.EPOLLRDHUP | syscall.EPOLLHUP | syscall.EPOLLERR

// register Control function, subscribe epoll event
func registerEpollEvent(conn net.Conn, watchEpollEvents uint32) error {
	rawConn, err := conn.(*net.TCPConn).SyscallConn()
	if err != nil {
		return err
	}

	efd, err := syscall.EpollCreate1(0)
	if err != nil {
		return err
	}
	
	var cerr error = nil

	err = rawConn.Control(func(fd uintptr) {
		event := &syscall.EpollEvent{Events: watchEpollEvents, Fd: int32(fd)}
		err = syscall.EpollCtl(efd, syscall.EPOLL_CTL_ADD, int(fd), event)
		if err != nil {
			cerr = err
			return
		}
	})
	
	if err != nil {
		return err
	}
	
	return cerr
}

// check if conn closed by peer
func checkConn(rawConn syscall.RawConn, efd int) error {

	var cerr error = nil
	
	err := rawConn.Control(func(fd uintptr) {
		events := make([]syscall.EpollEvent, 1)
		_, err := syscall.EpollWait(efd, events, 0)
		if err != nil {
			cerr = err
			return
		}
		if events[0].Events&watchEpollEvents != 0 {
			cerr = errors.New("conn has been closed by peer")
		}
	})
	
	if err != nil {
		return err
	}
	return cerr
}