// Package netchan provides an in-memory implementation of the [net.Listener] // interface, allowing programs to simulate network connections using channels // and pipes instead of real network sockets.
package netchan import ( ) var _ net.Listener = (*Listener)(nil) // ErrClosed indicates that the [Listener] has been closed and no further // operations can be performed. var ErrClosed = errors.New("netchan: closed") // chanAddr implements the [net.chanAddr] interface representing the network // address of an in-memory [Listener]. Since connections are in-memory, chanAddr // returns constant values indicating the connection uses channels. type chanAddr struct{} // Network returns the name of the network, which is always "chan". func (chanAddr) () string { return "chan" } // String returns a string representation of the Addr, which is always "chan". func (chanAddr) () string { return "chan" } // Listener implements the [net.Listener] interface using channels for // accepting connections. Incoming connections are sent on an internal // channel and accepted by Accept calls. The Listener can be closed to // unblock Accept calls and signal shutdown. type Listener struct { addr net.Addr conn chan net.Conn done chan struct{} once sync.Once } // NewListener returns a new Listener with the default address. func () *Listener { return NewListenerAddr(chanAddr{}) } // NewListenerAddr returns a new Listener instance with the specified network // address. func ( net.Addr) *Listener { return &Listener{ addr: , conn: make(chan net.Conn), done: make(chan struct{}), } } // Accept waits for and returns the next connection sent to the listener’s // connection channel. If the listener is closed, Accept returns [ErrClosed]. func ( *Listener) () (net.Conn, error) { select { // Accept should return an error after Close. case <-.done: return nil, ErrClosed default: } : select { case , := <-.conn: // Handle unlikely case of close(l.C()). if ! { return nil, ErrClosed } // Allow sending nil connection to wait for Accept call. if == nil { goto } return , nil // Unblock Accept when Close is called. case <-.done: return nil, ErrClosed } } // Addr implements the [net.Listener] interface. func ( *Listener) () net.Addr { return chanAddr{} } // Close closes the listener and unblocks all Accept calls. func ( *Listener) () error { .once.Do(func() { close(.done) }) return nil } // C returns the internal channel used to send [net.Conn] connections to the // Accept callers. The returned channel should not be closed; use Close method // to unblock Accept instead. // // Sending a nil connection on this channel is allowed and can be used // to wait for the Accept caller to be ready. func ( *Listener) () chan net.Conn { return .conn } // Done returns a channel that is closed when the Listener is closed. func ( *Listener) () <-chan struct{} { return .done } // Dial creates a new in-memory connection pair using net.Pipe and sends the // server side connection to the Listener’s internal connection channel. // // It returns the client side connection, which can be used to communicate // with the accepted connection on the Listener side. // // Dial blocks until the server side connection is accepted by a call to Accept, // or until the Listener is closed or the provided context is canceled. func ( *Listener) ( context.Context) (net.Conn, error) { , := net.Pipe() select { case .conn <- : return , nil case <-.done: return nil, ErrClosed case <-.Done(): return nil, .Err() } }