[network] Detect Half Closed Connection
Abstract
- Two way to detect
half closed connection
- common: read 1-byte with read deadline
- unix-like: subscribe epoll event syscall.EPOLLRDHUP | syscall.EPOLLHUP | syscall.EPOLLERR
- 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
}