def test_reuse_same_address_port(self): # NOTE: This test should ensure that same address port can be used by two # sockets. This to prevent accidental changes to socket options. In Windows # SO_REUSEADDR flag allows two sockets to bind to same address:port combination. # Windows should always use SO_EXCLUSIVEADDRUSE sock1 = sockets.create_server(self.HOST1, 0) try: _, PORT1 = sock1.getsockname() with pytest.raises(Exception): sockets.create_server(self.HOST1, PORT1) finally: sockets.close_socket(sock1)
def test_reuse_same_port(self): try: sock1, sock2 = None, None sock1 = sockets.create_server(self.HOST1, 0) _, PORT1 = sock1.getsockname() sock2 = sockets.create_server(self.HOST2, PORT1) assert sock1.getsockname() == (self.HOST1, PORT1) assert sock2.getsockname() == (self.HOST2, PORT1) except Exception: pytest.fail() finally: if sock1 is not None: sockets.close_socket(sock1) if sock2 is not None: sockets.close_socket(sock2)
def enable_attach(dont_trace_start_patterns, dont_trace_end_patterns): # Errors below are logged with level="info", because the caller might be catching # and handling exceptions, and we don't want to spam their stderr unnecessarily. import subprocess if hasattr(enable_attach, "adapter"): raise AssertionError("enable_attach() can only be called once per process") server_access_token = compat.force_str(codecs.encode(os.urandom(32), "hex")) try: endpoints_listener = sockets.create_server("127.0.0.1", 0, timeout=5) except Exception as exc: log.exception("Can't listen for adapter endpoints:") raise RuntimeError("can't listen for adapter endpoints: " + str(exc)) endpoints_host, endpoints_port = endpoints_listener.getsockname() log.info( "Waiting for adapter endpoints on {0}:{1}...", endpoints_host, endpoints_port ) adapter_args = [ sys.executable, os.path.dirname(adapter.__file__), "--for-server", str(endpoints_port), "--host", options.host, "--port", str(options.port), "--server-access-token", server_access_token, ] if log.log_dir is not None: adapter_args += ["--log-dir", log.log_dir] log.info("enable_attach() spawning adapter: {0!j}", adapter_args) # On Windows, detach the adapter from our console, if any, so that it doesn't # receive Ctrl+C from it, and doesn't keep it open once we exit. creationflags = 0 if sys.platform == "win32": creationflags |= 0x08000000 # CREATE_NO_WINDOW creationflags |= 0x00000200 # CREATE_NEW_PROCESS_GROUP # Adapter will outlive this process, so we shouldn't wait for it. However, we # need to ensure that the Popen instance for it doesn't get garbage-collected # by holding a reference to it in a non-local variable, to avoid triggering # https://bugs.python.org/issue37380. try: enable_attach.adapter = subprocess.Popen( adapter_args, close_fds=True, creationflags=creationflags ) if os.name == "posix": # It's going to fork again to daemonize, so we need to wait on it to # clean it up properly. enable_attach.adapter.wait() else: # Suppress misleading warning about child process still being alive when # this process exits (https://bugs.python.org/issue38890). enable_attach.adapter.returncode = 0 pydevd.add_dont_terminate_child_pid(enable_attach.adapter.pid) except Exception as exc: log.exception("Error spawning debug adapter:", level="info") raise RuntimeError("error spawning debug adapter: " + str(exc)) try: sock, _ = endpoints_listener.accept() try: sock.settimeout(None) sock_io = sock.makefile("rb", 0) try: endpoints = json.loads(sock_io.read().decode("utf-8")) finally: sock_io.close() finally: sockets.close_socket(sock) except socket.timeout: log.exception("Timed out waiting for adapter to connect:", level="info") raise RuntimeError("timed out waiting for adapter to connect") except Exception as exc: log.exception("Error retrieving adapter endpoints:", level="info") raise RuntimeError("error retrieving adapter endpoints: " + str(exc)) log.info("Endpoints received from adapter: {0!j}", endpoints) if "error" in endpoints: raise RuntimeError(str(endpoints["error"])) try: host = str(endpoints["server"]["host"]) port = int(endpoints["server"]["port"]) options.port = int(endpoints["ide"]["port"]) except Exception as exc: log.exception( "Error parsing adapter endpoints:\n{0!j}\n", endpoints, level="info" ) raise RuntimeError("error parsing adapter endpoints: " + str(exc)) log.info( "Adapter is accepting incoming IDE connections on {0}:{1}", options.host, options.port, ) _settrace( host=host, port=port, suspend=False, patch_multiprocessing=options.multiprocess, wait_for_ready_to_run=False, block_until_connected=True, dont_trace_start_patterns=dont_trace_start_patterns, dont_trace_end_patterns=dont_trace_end_patterns, access_token=server_access_token, client_access_token=options.client_access_token, ) log.info("pydevd is connected to adapter at {0}:{1}", host, port) return options.host, options.port
def main(args): from ptvsd import adapter from ptvsd.common import compat, log, sockets from ptvsd.adapter import ide, servers, sessions if args.for_server is not None: if os.name == "posix": # On POSIX, we need to leave the process group and its session, and then # daemonize properly by double-forking (first fork already happened when # this process was spawned). os.setsid() if os.fork() != 0: sys.exit(0) for stdio in sys.stdin, sys.stdout, sys.stderr: if stdio is not None: stdio.close() if args.log_stderr: log.stderr.levels |= set(log.LEVELS) if args.log_dir is not None: log.log_dir = args.log_dir log.to_file(prefix="ptvsd.adapter") log.describe_environment("ptvsd.adapter startup environment:") servers.access_token = args.server_access_token if args.for_server is None: adapter.access_token = compat.force_str( codecs.encode(os.urandom(32), "hex")) try: server_host, server_port = servers.listen() except Exception as exc: if args.for_server is None: raise endpoints = { "error": "Can't listen for server connections: " + str(exc) } else: endpoints = {"server": {"host": server_host, "port": server_port}} try: ide_host, ide_port = ide.listen(port=args.port) except Exception as exc: if args.for_server is None: raise endpoints = { "error": "Can't listen for IDE connections: " + str(exc) } else: endpoints["ide"] = {"host": ide_host, "port": ide_port} if args.for_server is not None: log.info( "Sending endpoints info to debug server at localhost:{0}:\n{1!j}", args.for_server, endpoints, ) try: sock = sockets.create_client() try: sock.settimeout(None) sock.connect(("127.0.0.1", args.for_server)) sock_io = sock.makefile("wb", 0) try: sock_io.write(json.dumps(endpoints).encode("utf-8")) finally: sock_io.close() finally: sockets.close_socket(sock) except Exception: raise log.exception( "Error sending endpoints info to debug server:") if "error" in endpoints: log.error("Couldn't set up endpoints; exiting.") sys.exit(1) listener_file = os.getenv("PTVSD_ADAPTER_ENDPOINTS") if listener_file is not None: log.info("Writing endpoints info to {0!r}:\n{1!j}", listener_file, endpoints) def delete_listener_file(): log.info("Listener ports closed; deleting {0!r}", listener_file) try: os.remove(listener_file) except Exception: log.exception("Failed to delete {0!r}", listener_file, level="warning") try: with open(listener_file, "w") as f: atexit.register(delete_listener_file) print(json.dumps(endpoints), file=f) except Exception: raise log.exception("Error writing endpoints info to file:") if args.port is None: ide.IDE("stdio") # These must be registered after the one above, to ensure that the listener sockets # are closed before the endpoint info file is deleted - this way, another process # can wait for the file to go away as a signal that the ports are no longer in use. atexit.register(servers.stop_listening) atexit.register(ide.stop_listening) servers.wait_until_disconnected() log.info( "All debug servers disconnected; waiting for remaining sessions...") sessions.wait_until_ended() log.info("All debug sessions have ended; exiting.")