Reputation: 2040
I love the way Go handles I/O multiplexing internally which epoll
and another mechanisms and schedules green threads (go-routine here) on its own giving the freedom to write synchronous code.
I know TCP sockets are non-blocking
and read
will give EAGAIN
when no data is available. Given that, conn.Read(buffer)
will detect this and blocks the go routine doing a connection read with no data available in the socket buffer. Is there a way to stop such go routine without closing the underlying connection. I am using a connection pool so closing the TCP connection won't make sense for me and want to return that connection back to the pool.
Here is the code to simulate such scenario:
func main() {
conn, _ := net.Dial("tcp", "127.0.0.1:9090")
// Spawning a go routine
go func(conn net.Conn) {
var message bytes.Buffer
for {
k := make([]byte, 255) // buffer
m, err := conn.Read(k) // blocks here
if err != nil {
if err != io.EOF {
fmt.Println("Read error : ", err)
} else {
fmt.Println("End of the file")
}
break // terminate loop if error
}
// converting bytes to string for printing
if m > 0 {
for _, b := range k {
message.WriteByte(b)
}
fmt.Println(message.String())
}
}
}(conn)
// prevent main from exiting
select {}
}
What are the other approaches can I take if it's not possible:
1) Call syscall.Read
and handle this manually. In this case, I need a way to check if the socket is readable before calling syscall.Read
otherwise I will end up wasting unnecessary CPU cycles. For my scenario, I think I can skip the event based polling thing and keep on calling syscall.Read
as there always be data in my use case.
2) Any suggestions :)
Upvotes: 2
Views: 2834
Reputation: 16440
func receive(conn net.TCPConn, kill <-chan struct{}) error {
// Spawn a goroutine to read from the connection.
data := make(chan []byte)
readErr := make(chan error)
go func() {
for {
b := make([]byte, 255)
_, err := conn.Read(b)
if err != nil {
readErr <- err
break
}
data <- b
}
}()
for {
select {
case b := <-data:
// Do something with `b`.
case err := <-readErr:
// Handle the error.
return err
case <-kill:
// Received kill signal, returning without closing the connection.
return nil
}
}
}
Send an empty struct to kill
from another goroutine to stop receiving from the connection. Here's a program that stops receiving after a second:
kill := make(chan struct{})
go func() {
if err := receive(conn, kill); err != nil {
log.Fatal(err)
}
}()
time.Sleep(time.Second)
kill <- struct{}{}
This might not be exactly what you're looking for, because the reading goroutine would still be blocked on Read
even after you send to kill
. However, the goroutine that handles incoming reads would terminate.
Upvotes: 1