diff --git a/ros2cli/ros2cli/daemon/__init__.py b/ros2cli/ros2cli/daemon/__init__.py index 42c5affdf..1c00795bf 100644 --- a/ros2cli/ros2cli/daemon/__init__.py +++ b/ros2cli/ros2cli/daemon/__init__.py @@ -151,6 +151,13 @@ def shutdown_handler(): pass +def serve_and_close(server: LocalXMLRPCServer, *, timeout: int = 2 * 60 * 60): + try: + serve(server, timeout=timeout) + finally: + server.server_close() + + def main(*, argv=None): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter) diff --git a/ros2cli/ros2cli/node/daemon.py b/ros2cli/ros2cli/node/daemon.py index c25a2d847..43342b4cf 100644 --- a/ros2cli/ros2cli/node/daemon.py +++ b/ros2cli/ros2cli/node/daemon.py @@ -46,7 +46,7 @@ def connected(self): for method in self._proxy.system.listMethods() if not method.startswith('system.') ] - except ConnectionRefusedError: + except (ConnectionRefusedError, ConnectionResetError): return False return True @@ -168,7 +168,7 @@ def spawn_daemon(args, timeout=None, debug=False): 'rmw_implementation': rclpy.get_rmw_implementation_identifier()} daemonize( - functools.partial(daemon.serve, server), + functools.partial(daemon.serve_and_close, server), tags=tags, timeout=timeout, debug=debug) finally: server.server_close() diff --git a/ros2cli/ros2cli/xmlrpc/local_server.py b/ros2cli/ros2cli/xmlrpc/local_server.py index 9e3e9e476..abe14a953 100644 --- a/ros2cli/ros2cli/xmlrpc/local_server.py +++ b/ros2cli/ros2cli/xmlrpc/local_server.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os import socket -import struct # Import SimpleXMLRPCRequestHandler to re-export it. from xmlrpc.server import SimpleXMLRPCRequestHandler # noqa from xmlrpc.server import SimpleXMLRPCServer @@ -32,20 +32,12 @@ def get_local_ipaddrs(): class LocalXMLRPCServer(SimpleXMLRPCServer): - allow_reuse_address = False - - def server_bind(self): - # Prevent listening socket from lingering in TIME_WAIT state after close() - self.socket.setsockopt( - socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0)) - super(LocalXMLRPCServer, self).server_bind() - - def get_request(self): - # Prevent accepted socket from lingering in TIME_WAIT state after close() - sock, addr = super(LocalXMLRPCServer, self).get_request() - sock.setsockopt( - socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0)) - return sock, addr + # Allow re-binding even if another server instance was recently bound (i.e. we are still in + # TCP TIME_WAIT). This is already the default behavior on Windows, and further SO_REUSEADDR can + # lead to undefined behavior on Windows; see + # https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse. # noqa + # So we don't set the option for Windows. + allow_reuse_address = False if os.name == 'nt' else True def verify_request(self, request, client_address): if client_address[0] not in get_local_ipaddrs():