Skip to content

Conversation

@duijf
Copy link

@duijf duijf commented Oct 18, 2025

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()
}

`Accept()` on a listener returned by `net.Listen()` will return an error
once the underlying listener is closed. Some packages in the ecosystem
rely on this behavior for orderly server shutdown.

Before this patch, `VirtioSocketDevice.Accept()` would never return once
the underlying listener was closed as `acceptch` was never closed. This
patch fixes this.
@duijf
Copy link
Author

duijf commented Nov 14, 2025

@Code-Hex let me know if any additional context would be helpful here :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant