33
44import errno
55from contextlib import contextmanager , suppress
6- from os import PathLike , stat , unlink
6+ from os import stat , unlink
7+ from os .path import exists
78from stat import S_ISSOCK
89from typing import TYPE_CHECKING , Final , overload
910
1819
1920 from typing_extensions import Buffer
2021
21- from ._socket import SocketType
22+ from ._socket import AddressFormat , SocketType
2223
2324# XX TODO: this number was picked arbitrarily. We should do experiments to
2425# tune it. (Or make it dynamic -- one idea is to start small and increase it
@@ -358,33 +359,25 @@ class SocketListener(Listener[SocketStream]):
358359 incoming connections as :class:`SocketStream` objects.
359360
360361 Args:
361-
362362 socket: The Trio socket object to wrap. Must have type ``SOCK_STREAM``,
363363 and be listening.
364364
365- path: Used for keeping track of which path a Unix socket is bound
366- to. If not ``None``, :meth:`aclose` will unlink this path.
367- File must have socket mode flag set.
368-
369365 Note that the :class:`SocketListener` "takes ownership" of the given
370- socket; closing the :class:`SocketListener` will also close the socket.
366+ socket; closing the :class:`SocketListener` will also close the
367+ socket, and if it's a Unix socket, it will also unlink the leftover
368+ socket file that the Unix socket is bound to.
371369
372370 .. attribute:: socket
373371
374372 The Trio socket object that this stream wraps.
375373
376- .. attribute:: path
377-
378- The path to unlink in :meth:`aclose` that a Unix socket is bound to.
379-
380374 """
381375
382- __slots__ = ("path" , " socket" )
376+ __slots__ = ("socket" , )
383377
384378 def __init__ (
385379 self ,
386380 socket : SocketType ,
387- path : str | bytes | PathLike [str ] | PathLike [bytes ] | None = None ,
388381 ) -> None :
389382 if not isinstance (socket , tsocket .SocketType ):
390383 raise TypeError ("SocketListener requires a Trio socket object" )
@@ -398,11 +391,8 @@ def __init__(
398391 else :
399392 if not listening :
400393 raise ValueError ("SocketListener requires a listening socket" )
401- if path is not None and not S_ISSOCK (stat (path ).st_mode ):
402- raise ValueError ("Specified path must be a Unix socket file" )
403394
404395 self .socket = socket
405- self .path = path
406396
407397 async def accept (self ) -> SocketStream :
408398 """Accept an incoming connection.
@@ -433,8 +423,21 @@ async def accept(self) -> SocketStream:
433423 return SocketStream (sock )
434424
435425 async def aclose (self ) -> None :
436- """Close this listener and its underlying socket."""
426+ """Close this listener, its underlying socket, and for Unix sockets unlink the socket file."""
427+ is_unix_socket = self .socket .family == getattr (tsocket , "AF_UNIX" , None )
428+
429+ path : AddressFormat | None = None
430+ if is_unix_socket :
431+ # If unix socket, need to get path before we close socket
432+ # or OS errors
433+ path = self .socket .getsockname ()
437434 self .socket .close ()
438- if self .path is not None :
439- unlink (self .path )
435+ # If unix socket, clean up socket file that gets left behind.
436+ if (
437+ is_unix_socket
438+ and path is not None
439+ and exists (path )
440+ and S_ISSOCK (stat (path ).st_mode )
441+ ):
442+ unlink (path )
440443 await trio .lowlevel .checkpoint ()
0 commit comments