|
|
|
@ -17,17 +17,27 @@ import os |
|
|
|
|
import socket |
|
|
|
|
|
|
|
|
|
_DEFAULT_SOCK_OPTION = socket.SO_REUSEADDR if os.name == 'nt' else socket.SO_REUSEPORT |
|
|
|
|
_UNRECOVERABLE_ERRORS = ('Address already in use',) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _exception_is_unrecoverable(e): |
|
|
|
|
for error in _UNRECOVERABLE_ERRORS: |
|
|
|
|
if error in str(e): |
|
|
|
|
return True |
|
|
|
|
return False |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_socket(bind_address='localhost', |
|
|
|
|
port=0, |
|
|
|
|
listen=True, |
|
|
|
|
sock_options=(_DEFAULT_SOCK_OPTION,)): |
|
|
|
|
"""Opens a socket bound to an arbitrary port. |
|
|
|
|
"""Opens a socket. |
|
|
|
|
|
|
|
|
|
Useful for reserving a port for a system-under-test. |
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
|
bind_address: The host to which to bind. |
|
|
|
|
port: The port to bind. |
|
|
|
|
listen: A boolean value indicating whether or not to listen on the socket. |
|
|
|
|
sock_options: A sequence of socket options to apply to the socket. |
|
|
|
|
|
|
|
|
@ -47,19 +57,23 @@ def get_socket(bind_address='localhost', |
|
|
|
|
sock = socket.socket(address_family, socket.SOCK_STREAM) |
|
|
|
|
for sock_option in _sock_options: |
|
|
|
|
sock.setsockopt(socket.SOL_SOCKET, sock_option, 1) |
|
|
|
|
sock.bind((bind_address, 0)) |
|
|
|
|
sock.bind((bind_address, port)) |
|
|
|
|
if listen: |
|
|
|
|
sock.listen(1) |
|
|
|
|
return bind_address, sock.getsockname()[1], sock |
|
|
|
|
except socket.error: |
|
|
|
|
except socket.error as socket_error: |
|
|
|
|
sock.close() |
|
|
|
|
continue |
|
|
|
|
if _exception_is_unrecoverable(socket_error): |
|
|
|
|
raise |
|
|
|
|
else: |
|
|
|
|
continue |
|
|
|
|
raise RuntimeError("Failed to bind to {} with sock_options {}".format( |
|
|
|
|
bind_address, sock_options)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager |
|
|
|
|
def bound_socket(bind_address='localhost', |
|
|
|
|
port=0, |
|
|
|
|
listen=True, |
|
|
|
|
sock_options=(_DEFAULT_SOCK_OPTION,)): |
|
|
|
|
"""Opens a socket bound to an arbitrary port. |
|
|
|
@ -68,6 +82,7 @@ def bound_socket(bind_address='localhost', |
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
|
bind_address: The host to which to bind. |
|
|
|
|
port: The port to bind. |
|
|
|
|
listen: A boolean value indicating whether or not to listen on the socket. |
|
|
|
|
sock_options: A sequence of socket options to apply to the socket. |
|
|
|
|
|
|
|
|
@ -77,6 +92,7 @@ def bound_socket(bind_address='localhost', |
|
|
|
|
- the port to which the socket is bound |
|
|
|
|
""" |
|
|
|
|
host, port, sock = get_socket(bind_address=bind_address, |
|
|
|
|
port=port, |
|
|
|
|
listen=listen, |
|
|
|
|
sock_options=sock_options) |
|
|
|
|
try: |
|
|
|
|