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 }