github.com/haraldrudell/parl@v0.4.176/pnet/socket-listener.go (about)

     1  /*
     2  © 2021–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package pnet
     7  
     8  import (
     9  	"errors"
    10  	"net"
    11  	"net/netip"
    12  	"sync"
    13  	"sync/atomic"
    14  
    15  	"github.com/haraldrudell/parl"
    16  	"github.com/haraldrudell/parl/perrors"
    17  )
    18  
    19  const (
    20  	// when Accept exits, errors are sent [SocketListener.close]
    21  	threadExitSendsErrorOnChannel = true
    22  	// when a consumeer invoked Close, errors are not sent [SocketListener.close]
    23  	consumerClose = false
    24  )
    25  
    26  // SocketListener is a generic wrapper for [net.Listener]
    27  //   - not intended for direct use, instead use specific implementations like:
    28  //   - — [TCPListener]
    29  //   - C is the type of connection the handler function receives:
    30  //   - — net.Conn, *net.TCPConn, …
    31  //   - panic-handled connection threads
    32  //   - Ch: real-time error channel or collecting errors after close: Err
    33  //   - WaitCh: idempotent waitable thread-terminating Close
    34  //   - SocketListener methods are thread-safe
    35  type SocketListener[C net.Conn] struct {
    36  	// netListener can be type aasserted to a listener for
    37  	// transport socket type
    38  	netListener net.Listener
    39  	// network for listening tcp tcp4 tcp6…
    40  	//	- the type of near and far socket addresses
    41  	//	- network should be compatible wwith transport
    42  	network Network
    43  	// transport indicates what listener implementation netListener
    44  	// can be type asserted to: udp/tcp/ip/unix
    45  	transport SocketTransport
    46  	// stateLock attains integrity by making mutually exclusive:
    47  	//	- [SocketListener.Listen]
    48  	//	- [SocketListener.close]
    49  	//	- [SocketListener.setAcceptState]
    50  	stateLock sync.Mutex
    51  	// state controls the singleton statee cycle: 0 soListening soAccepting soClosing soClosed
    52  	//	- writes behind stateLock for integrity
    53  	state parl.Atomic32[socketState]
    54  	// allows waiting for all pending connections
    55  	connWait sync.WaitGroup
    56  	// allows waiting for accept thread to exit
    57  	acceptWait sync.WaitGroup
    58  	// allows waiting for close complete
    59  	closeWait chan struct{}
    60  	// the channel never closes
    61  	errs parl.ErrSlice
    62  	// cached error from [SocketListener.close]
    63  	closeErr     atomic.Pointer[error]
    64  	threadSource atomic.Pointer[ThreadSource[C]]
    65  
    66  	// SocketListener.AcceptConnections
    67  
    68  	// the function receiving new connections
    69  	handler func(C)
    70  }
    71  
    72  // NewSocketListener returns object listening for socket connections
    73  //   - C is the type of net.Listener the handler function provided to [SocketListener.AcceptConnections]
    74  //   - SocketListener provides asynchronous error handling
    75  //   - handler must invoke net.Conn.Close
    76  //     -
    77  //   - default threading is one virtual thread per connection
    78  //   - [SocketListener.SetThreadSource] allows for any thread model replacing handle
    79  func NewSocketListener[C net.Conn](
    80  	listener net.Listener,
    81  	network Network,
    82  	transport SocketTransport,
    83  ) (socket *SocketListener[C]) {
    84  	if listener == nil {
    85  		panic(perrors.NewPF("listener cannot be nil"))
    86  	} else if !network.IsValid() {
    87  		panic(perrors.ErrorfPF("invalid network: %s", network))
    88  	} else if !transport.IsValid() {
    89  		panic(perrors.ErrorfPF("invalid transport: %s", transport))
    90  	}
    91  	return &SocketListener[C]{
    92  		netListener: listener,
    93  		network:     network,
    94  		transport:   transport,
    95  		closeWait:   make(chan struct{}),
    96  	}
    97  }
    98  
    99  // Listen binds listening to a near socket
   100  //   - socketString is host:port "1.2.3.4:80" "wikipedia.com:443" "/some/unix/socket"
   101  //   - — for TCP UDP IP host must resolve to an assigned near IP address
   102  //   - — — if host is blank, it is for localhost
   103  //   - — — to avoid DNS resolution host should be blank or literal IP address "1.2.3.4:0"
   104  //   - — for TCP UDP port must be literal port number 0…65534 where 0 means a temporary port
   105  //   - Listen can be repeatedly invoked until it succeeds
   106  func (s *SocketListener[C]) Listen(socketString string) (err error) {
   107  	s.stateLock.Lock()
   108  	defer s.stateLock.Unlock()
   109  
   110  	switch s.state.Load() {
   111  	case soListening, soAccepting:
   112  		err = perrors.NewPF("invoked on listening socket")
   113  		return
   114  	case soClosing, soClosed:
   115  		err = perrors.NewPF("invoked on closed socket")
   116  		return
   117  	}
   118  
   119  	switch s.transport {
   120  	case TransportTCP:
   121  		// resolve near socket address
   122  		var tcpAddr *net.TCPAddr
   123  		if tcpAddr, err = net.ResolveTCPAddr(s.network.String(), socketString); perrors.Is(&err, "ResolveTCPAddr: '%w'", err) {
   124  			return
   125  		}
   126  
   127  		// attempt to listen
   128  		var netTCPListener *net.TCPListener
   129  		if netTCPListener, err = net.ListenTCP(s.network.String(), tcpAddr); perrors.Is(&err, "ListenTCP: %w", err) {
   130  			return
   131  		}
   132  
   133  		// copy socket to TCPListener storage
   134  		var listenerp = s.netListener.(*net.TCPListener)
   135  		*listenerp = *netTCPListener
   136  	default:
   137  		err = perrors.ErrorfPF("unimplemented transport: %s", s.transport)
   138  		return
   139  	}
   140  
   141  	// update state to listening
   142  	s.state.Store(soListening)
   143  
   144  	return
   145  }
   146  
   147  // Ch returns a real-time error channel
   148  //   - the channel never closes
   149  //   - unread errors can also be collected using [TCPListener.Err]
   150  func (s *SocketListener[C]) Errs() (errs parl.Errs) { return &s.errs }
   151  
   152  func (s *SocketListener[C]) SetThreadSource(threadSource ThreadSource[C]) {
   153  	s.threadSource.Store(&threadSource)
   154  }
   155  
   156  // AcceptConnections is a blocking function handling inbound connections
   157  //   - handler: must be non-nil or a ThreadSource must be active
   158  //   - goodClose true: Accept ended with net.ErrClosed
   159  //   - goodClose false: Accept ended with an unknown error
   160  //   - AcceptConnections:
   161  //   - — accepts connections until the socket is closed by invoking Close
   162  //   - — can only be invoked once and socket state must be Listening
   163  //   - handler or ThreadSouce must invoke [net.Conn.Close]
   164  func (s *SocketListener[C]) AcceptConnections(handler func(C)) (goodClose bool) {
   165  	var isPanic bool
   166  	var cReceiver ConnectionReceiver[C]
   167  	defer s.close(threadExitSendsErrorOnChannel)
   168  	if err := s.setAcceptState(); err != nil {
   169  		s.errs.AddError(err)
   170  		return
   171  	}
   172  	defer s.acceptWait.Done()                  // indicate accept thread exited
   173  	defer s.waitForConns(&cReceiver, &isPanic) // wait for connection goroutines
   174  	defer parl.Recover2(func() parl.DA { return parl.A() }, nil, s.errs.AddError)
   175  
   176  	s.handler = handler
   177  	var err error
   178  	var conn net.Conn
   179  	for {
   180  
   181  		// obtain connection receiver from possible thread source
   182  		if cReceiver, err = s.getReceiver(); err != nil {
   183  			s.errs.AddError(err)
   184  			return // [ThreadSource.Receiver] failed
   185  		} else if cReceiver != nil {
   186  			s.connWait.Add(1)
   187  		} else if handler == nil {
   188  			s.errs.AddError(perrors.NewPF("handler cannot be nil"))
   189  			return // no receiver no handler return
   190  		}
   191  
   192  		// block waiting for incoming connection
   193  		if conn, err = s.netListener.Accept(); err != nil { // blocking: either a connection or an error
   194  			if opError, ok := err.(*net.OpError); ok {
   195  				if goodClose = errors.Is(opError.Err, net.ErrClosed); goodClose {
   196  					return // use of closed: assume shutdown: ListenTCP4 is closed
   197  				}
   198  			}
   199  			s.errs.AddError(perrors.ErrorfPF("TCPListener.Accept: %T '%[1]w'", err)) // some error
   200  			continue
   201  		}
   202  
   203  		// type assert connection: closes conn if assertion fails
   204  		var c C
   205  		if c, err = s.assertConnection(conn); err != nil {
   206  			s.errs.AddError(err)
   207  			return // connection cannot asserted to C return: never happens
   208  		}
   209  
   210  		// invoke connection handler
   211  		if cReceiver != nil {
   212  			s.invokeHandle(c, cReceiver, &isPanic)
   213  		} else {
   214  			s.connWait.Add(1)
   215  			go s.invokeHandler(c)
   216  		}
   217  	}
   218  }
   219  
   220  // IsAccept indicates whether the listener is functional and
   221  // accepting incoming connections
   222  func (s *SocketListener[C]) IsAccept() (isAcceptThread bool) { return s.state.Load() == soAccepting }
   223  
   224  // WaitCh returns a channel that closes when [] completes
   225  //   - ListenTCP4.Close needs to have been invoked for the channel to close
   226  func (s *SocketListener[C]) WaitCh() (closeWait chan struct{}) {
   227  	return s.closeWait
   228  }
   229  
   230  func (s *SocketListener[C]) AddrPort() (addrPort netip.AddrPort, err error) {
   231  	var netAddr = s.netListener.Addr()
   232  	switch a := netAddr.(type) {
   233  	case *net.TCPAddr:
   234  		addrPort = a.AddrPort()
   235  	case *net.UDPAddr:
   236  		addrPort = a.AddrPort()
   237  	case *net.UnixAddr:
   238  		return // unix sockets do not have address or port
   239  	case *net.IPAddr:
   240  		var addr netip.Addr
   241  		if addr, err = netip.ParseAddr(a.String()); perrors.IsPF(&err, "netip.ParseAddr %w", err) {
   242  			return
   243  		}
   244  		addrPort = netip.AddrPortFrom(addr, 0)
   245  	default:
   246  		addrPort, err = netip.ParseAddrPort(netAddr.String())
   247  		perrors.IsPF(&err, "netip.ParseAddrPort %w", err)
   248  	}
   249  	return
   250  }
   251  
   252  // Err returns all unread errors
   253  //   - errors can also be read using [TCPListener.Ch]
   254  func (s *SocketListener[C]) Err(errp *error) {
   255  	if errp == nil {
   256  		panic(perrors.NewPF("errp cannot be nil"))
   257  	}
   258  	for _, err := range s.errs.Errors() {
   259  		*errp = perrors.AppendError(*errp, err)
   260  	}
   261  }
   262  
   263  // Close ensures the socket is closed
   264  //   - socket guaranteed to be close on return
   265  //   - idempotent panic-free awaitable thread-safe
   266  func (s *SocketListener[C]) Close() (err error) {
   267  	_, err = s.close(consumerClose)
   268  	return
   269  }
   270  
   271  // setAcceptState transitions from [soListening] to [soAccepting]
   272  // in critical section
   273  func (s *SocketListener[C]) setAcceptState() (err error) {
   274  	s.stateLock.Lock()
   275  	defer s.stateLock.Unlock()
   276  
   277  	switch s.state.Load() {
   278  	case soListening:
   279  		s.state.Store(soAccepting)
   280  		s.acceptWait.Add(1)
   281  	case 0:
   282  		err = perrors.NewPF("socket not listening")
   283  	case soAccepting:
   284  		err = perrors.NewPF("invoked on accepting socket")
   285  	case soClosing, soClosed:
   286  		err = perrors.NewPF("invoked on closed socket")
   287  	}
   288  	return
   289  }
   290  
   291  // close closes [SocketListener.netListener]
   292  //   - only the
   293  func (s *SocketListener[C]) close(sendError bool) (didClose bool, err error) {
   294  	if s.state.Load() == soClosed {
   295  		if ep := s.closeErr.Load(); ep != nil {
   296  			err = *ep
   297  		}
   298  		return // already closed return
   299  	}
   300  	s.stateLock.Lock()
   301  	defer s.stateLock.Unlock()
   302  
   303  	// select closing invocation
   304  	if didClose = s.state.Load() != soClosed; !didClose {
   305  		if ep := s.closeErr.Load(); ep != nil {
   306  			err = *ep
   307  		}
   308  		return // already closed return
   309  	}
   310  
   311  	// execute close
   312  	s.state.Store(soClosing)
   313  	defer close(s.closeWait)
   314  	defer s.state.Store(soClosed)
   315  	defer s.acceptWait.Wait()
   316  	if parl.Close(s.netListener, &err); perrors.Is(&err, "TCPListener.Close %w", err) {
   317  		s.closeErr.Store(&err)
   318  		if sendError {
   319  			s.errs.AddError(err)
   320  		}
   321  	}
   322  
   323  	return
   324  }
   325  
   326  func (s *SocketListener[C]) invokeHandle(connImpl C, cReceiver ConnectionReceiver[C], isPanic *bool) {
   327  	*isPanic = true
   328  
   329  	// TODO 240430: on panic, it is unknown whether the socket was closed
   330  	//	- parl.IdempotentCloser cannot be used, because connImp is an implementation C
   331  	//	- do nothing for now
   332  	cReceiver.Handle(connImpl)
   333  
   334  	*isPanic = false
   335  }
   336  
   337  func (s *SocketListener[C]) waitForConns(cReceiverp *ConnectionReceiver[C], isPanic *bool) {
   338  	_ = isPanic
   339  	if cReceiver := *cReceiverp; cReceiver != nil {
   340  		cReceiver.Shutdown()
   341  	}
   342  	// // if there is panic, it connWait is uncertain
   343  	// if *isPanic {
   344  	// 	return
   345  	// }
   346  	s.connWait.Wait() // wait for connection goroutines
   347  }
   348  
   349  // invokeHandler is a goroutine executing the handler function for a new connection
   350  //   - invokeHandler recovers panic in handler function
   351  func (s *SocketListener[C]) invokeHandler(connImpl C) {
   352  	defer s.connWait.Done()
   353  	defer parl.Recover2(func() parl.DA { return parl.A() }, nil, s.errs.AddError)
   354  
   355  	s.handler(connImpl)
   356  }
   357  
   358  // obtain handler from possible thread source
   359  func (s *SocketListener[C]) getReceiver() (cReceiver ConnectionReceiver[C], err error) {
   360  
   361  	var ts ThreadSource[C]
   362  	if tsp := s.threadSource.Load(); tsp != nil {
   363  		ts = *tsp
   364  	}
   365  	if ts == nil {
   366  		return
   367  	}
   368  
   369  	if cReceiver, err = ts.Receiver(&s.connWait, s.errs.AddError); err != nil {
   370  		return // error from [ThreadSource.Receiver]
   371  	} else if cReceiver == nil {
   372  		err = perrors.NewPF("Received nil ConnectionReceiver")
   373  		return // [ThreadSource.Receiver] returned nil
   374  	}
   375  
   376  	return // good non-nil return, err nil
   377  }
   378  
   379  // type assert connection
   380  func (s *SocketListener[C]) assertConnection(conn net.Conn) (c C, err error) {
   381  
   382  	var ok bool
   383  	if c, ok = conn.(C); ok {
   384  		return
   385  	}
   386  
   387  	err = perrors.ErrorfPF("connection assertion to %T failed for type %T", c, conn)
   388  	var e error
   389  	parl.Close(conn, &e)
   390  	if e != nil {
   391  		err = perrors.AppendError(err, e)
   392  	}
   393  	return
   394  }