Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net: TCP server did NOT Close Conn when I close connection #29989

Closed
liuzheng opened this issue Jan 30, 2019 · 9 comments

Comments

Projects
None yet
5 participants
@liuzheng
Copy link

commented Jan 30, 2019

What version of Go are you using (go version)?

$ go version
go version go1.11 darwin/amd64

Does this issue reproduce with the latest release?

I'm think so

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/xxxxx/Library/Caches/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/opt/gopath"
GOPROXY=""
GORACE=""
GOROOT="/opt/go"
GOTMPDIR=""
GOTOOLDIR="/opt/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/0d/8cgyz4bx1s771vg_611m4cfm0000gn/T/go-build673412745=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

I want start a TCP server, when I use telnet or some client to connect the server, after I close client part, the server part still keep listen port and did not release it(the code did not go to defer function to close Conn).

package main

import (
	"fmt"
	"net"
	"os"
)

func main() {
	l, err := net.Listen("tcp", ":1234")
	if err != nil {
		fmt.Println("Error listening: ", err)
		os.Exit(1)
	}
	defer l.Close()
	for {
		conn, err := l.Accept()
		if err != nil {
			fmt.Println("Error accepting: ", err)
			os.Exit(1)
		}

		fmt.Printf("Start to receive message %s -> %s \n", conn.RemoteAddr(), conn.LocalAddr())

		go handle(conn)

		defer func() {
			fmt.Println("Close the connection in ", conn.RemoteAddr())
			conn.Close()
		}()
	}
}
func handle(conn net.Conn) {
	// do something
}
$ telnet localhost 1234
Trying ::1...
Connected to localhost.
Escape character is '^]'.
^]
telnet> Connection closed.

the server log:

Start to receive message [::1]:54092 -> [::1]:1234 

The netstat

$ netstat -A |grep 54092 
2fd4cc575c0e8c8b  960b59c tcp6       0      0  localhost.search-agent                        localhost.54092                               CLOSE_WAIT 
2fd4cc575c0e9dcb be2b0a98 tcp6       0      0  localhost.54092                               localhost.search-agent                        FIN_WAIT_2 

What did you expect to see?

I want the Conn close when I disconnect it, or close later.

What did you see instead?

2fd4cc575c0e8c8b  960b59c tcp6       0      0  localhost.search-agent                        localhost.54092                               CLOSE_WAIT 

always CLOSE_WAIT

@bruston

This comment has been minimized.

Copy link

commented Jan 30, 2019

Defer statements execute when the surrounding function returns or encounters a panic. Yours is inside an infinite for loop so it will never execute the close. You should move this logic to your handle function.

https://golang.org/ref/spec#Defer_statements

@liuzheng

This comment has been minimized.

Copy link
Author

commented Jan 30, 2019

@bruston Thank you first, defer just an example.
My purpose is Close the Connection, but there is no way to close when TCP connection close by accident.
I think if there have a way to listen the connection status, then use a channel to close it.

@nussjustin

This comment has been minimized.

Copy link
Contributor

commented Jan 30, 2019

As far as I know, at least on Linux and Mac, there is no way to get notified when the connection is closed, except reading from the socket and checking for a 0 byte read (which is converted to a (0, io.EOF) return value in Go).

You could enable keep alive on each connection via TCPConn.SetKeepAlive. This would ensure that the socket will be closed by the OS after some time, but you would still need to detect this by reading from the socket and closing the Conn in your code.

@bruston

This comment has been minimized.

Copy link

commented Jan 30, 2019

If the client closes the connection properly it should work just fine so long as you check and handle your read errors. If the connection is not properly closed (internet connection dies, router gets unplugged, etc) things get a bit more tricky.

You can set timeout deadlines for your read operations: https://golang.org/pkg/net/#TCPConn.SetReadDeadline and then close the connection yourself on a timeout error.

You might also be able to use KeepAlive: https://golang.org/pkg/net/#TCPConn.SetKeepAlive or implement your own kind of health check.

This doesn't seem like a bug, rather just the way TCP works. If you have more questions you're more than welcome to ask for assistance on any of the mediums listed here: https://github.com/golang/go/wiki/Questions

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

commented Jan 30, 2019

I don't see any actual bug here, so closing.

@liuzheng

This comment has been minimized.

Copy link
Author

commented Jan 31, 2019

@ianlancetaylor yes, actually there is not a bug, but I want Close those TCP closed connection automatically.
Now, I just use exec.Command to run lsof to check all CLOSE_WAIT connection and close it.

@liuzheng

This comment has been minimized.

Copy link
Author

commented Jan 31, 2019

Here is the code to clean CLOSE_WAIT, thank you for your advices.

package main

import (
	"fmt"
	"net"
	"os"
	"os/exec"
	"strconv"
	"regexp"
	"time"
	"strings"
	"github.com/liuzheng/golog"
)

var lsofout = ""

func main() {
	l, err := net.Listen("tcp", ":1234")
	if err != nil {
		fmt.Println("Error listening: ", err)
		os.Exit(1)
	}
	defer l.Close()

	ticker := time.NewTicker(time.Second * 5)
	go func() {
		for range ticker.C {
			lsof := exec.Command("lsof", "-p", strconv.Itoa(os.Getpid()), "-a", "-i", "tcp")
			grep := exec.Command("grep", "CLOSE_WAIT")
			lsofOut, _ := lsof.StdoutPipe()
			lsof.Start()
			grep.Stdin = lsofOut
			out, _ := grep.Output()
			lsofout = string(out)
		}
	}()

	for {
		conn, err := l.Accept()
		if err != nil {
			fmt.Println("Error accepting: ", err)
			os.Exit(1)
		}

		fmt.Printf("Start to receive message %s -> %s \n", conn.RemoteAddr(), conn.LocalAddr())

		go handle(conn)

		go func() {
			for range ticker.C {
				if strings.Index(lsofout, regexp.MustCompile(":[0-9]+$").FindAllStringSubmatch(conn.RemoteAddr().String(), -1)[0][0]+" ") > -1 {
					golog.Info("close", "Close the connection in %v", conn.RemoteAddr())
					conn.Close()
					return
				}
			}
		}()
	}
}
func handle(conn net.Conn) {
	// do something
}
@suutaku

This comment has been minimized.

Copy link

commented May 7, 2019

same problem.
I will take your way to solve that problem.
by the way, any other idea? @liuzheng

by the way, your code have a bug. every time accept will make new go func and only if strings.Index(lsofout, regexp.MustCompile(":[0-9]+$").FindAllStringSubmatch(conn.RemoteAddr().String(), -1)[0][0]+" ") > -1 will return.

@liuzheng

This comment has been minimized.

Copy link
Author

commented May 9, 2019

@suutaku sorry, I cannot understand that, in which case cause it become a bug?
This go func() is made for a watchdog to close the connection conn.Close().
I know in each connection will create go func is a dirty way, and the regular expression cause a lot CPU time, but there always have solutions to solve it.
We can use some event engine, but this is out of the topic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.