socket: Make Accept() return after Close() is called
Before this patch, VirtioSocketDevice.Accept() would never return after Close() was called. This is inconsistent with the behaviour of Accept() and Close() on a net.Listener from the stdlib.
The accept loops of many server implementations rely on Accept() returning an error when the listener is closed. Without this behavior, these accept loops can hang indefinitely, preventing servers from shutting down properly.
This patch changes the Close() method to close acceptch. Before we close the channel, we send an error that can be returned to the caller of Accept(), ensuring that accept loops can exit cleanly when the listener is closed.
Epand the code example below for more context on accept loops.
Accept loop example
Accept loops are commonly written like so:
package main
import (
"fmt"
"net"
"time"
"golang.org/x/sync/errgroup"
)
func runServer(listener net.Listener) error {
fmt.Println("Starting server...")
defer fmt.Println("Exiting server loop")
for {
conn, err := listener.Accept()
if err != nil {
return err // Exit when listener is closed
}
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
// Something...
}
func main() {
var g errgroup.Group
listener, _ := net.Listen("tcp", ":8080")
g.Go(func() error {
return runServer(listener)
})
// Simulate shutdown signal after some time
g.Go(func() error {
time.Sleep(3 * time.Second)
defer fmt.Println("Shutting down server")
listener.Close()
return nil
})
// TCP listeners exit normally.
// Before this patch, VirtioSocketDevices does not uphold
// the same contract. Accept() would block indefinitely before
// this patch which in turn blocks g.Wait()
g.Wait()
}
@Code-Hex let me know if any additional context would be helpful here :)