def _finalize(self, why, terminate_debuggee): # If the client started a session, and then disconnected before issuing "launch" # or "attach", the main thread will be blocked waiting for the first server # connection to come in - unblock it, so that we can exit. servers.dont_wait_for_first_connection() if self.server: if self.server.is_connected: if terminate_debuggee and self.launcher and self.launcher.is_connected: # If we were specifically asked to terminate the debuggee, and we # can ask the launcher to kill it, do so instead of disconnecting # from the server to prevent debuggee from running any more code. self.launcher.terminate_debuggee() else: # Otherwise, let the server handle it the best it can. try: self.server.channel.request( "disconnect", {"terminateDebuggee": terminate_debuggee}) except Exception: pass self.server.detach_from_session() if self.launcher and self.launcher.is_connected: # If there was a server, we just disconnected from it above, which should # cause the debuggee process to exit - so let's wait for that first. if self.server: log.info('{0} waiting for "exited" event...', self) if not self.wait_for( lambda: self.launcher.exit_code is not None, timeout=5): log.warning('{0} timed out waiting for "exited" event.', self) # Terminate the debuggee process if it's still alive for any reason - # whether it's because there was no server to handle graceful shutdown, # or because the server couldn't handle it for some reason. self.launcher.terminate_debuggee() # Wait until the launcher message queue fully drains. There is no timeout # here, because the final "terminated" event will only come after reading # user input in wait-on-exit scenarios. log.info("{0} waiting for {1} to disconnect...", self, self.launcher) self.wait_for(lambda: not self.launcher.is_connected) try: self.launcher.channel.close() except Exception: log.swallow_exception() if self.client: if self.client.is_connected: # Tell the client that debugging is over, but don't close the channel until it # tells us to, via the "disconnect" request. try: self.client.channel.send_event("terminated") except Exception: pass if (self.client.start_request is not None and self.client.start_request.command == "launch"): servers.stop_serving() log.info( '"launch" session ended - killing remaining debuggee processes.' ) pids_killed = set() if self.launcher and self.launcher.pid is not None: # Already killed above. pids_killed.add(self.launcher.pid) while True: conns = [ conn for conn in servers.connections() if conn.pid not in pids_killed ] if not len(conns): break for conn in conns: log.info("Killing {0}", conn) try: os.kill(conn.pid, signal.SIGTERM) except Exception: log.swallow_exception("Failed to kill {0}", conn) pids_killed.add(conn.pid)
def handle(self, request): assert request.is_request("launch", "attach") if self._initialize_request is None: raise request.isnt_valid("Session is not initialized yet") if self.launcher or self.server: raise request.isnt_valid("Session is already started") self.session.no_debug = request("noDebug", json.default(False)) if self.session.no_debug: servers.dont_wait_for_first_connection() self.session.debug_options = debug_options = set( request("debugOptions", json.array(unicode))) f(self, request) if self.server: self.server.initialize(self._initialize_request) self._initialize_request = None arguments = request.arguments if self.launcher: if "RedirectOutput" in debug_options: # The launcher is doing output redirection, so we don't need the # server to do it, as well. arguments = dict(arguments) arguments["debugOptions"] = list(debug_options - {"RedirectOutput"}) if arguments.get("redirectOutput"): arguments = dict(arguments) del arguments["redirectOutput"] # pydevd doesn't send "initialized", and responds to the start request # immediately, without waiting for "configurationDone". If it changes # to conform to the DAP spec, we'll need to defer waiting for response. try: self.server.channel.request(request.command, arguments) except messaging.MessageHandlingError as exc: exc.propagate(request) if self.session.no_debug: self.start_request = request self.has_started = True request.respond({}) self._propagate_deferred_events() return if {"WindowsClient", "Windows"} & debug_options: client_os_type = "WINDOWS" elif {"UnixClient", "UNIX"} & debug_options: client_os_type = "UNIX" else: client_os_type = "WINDOWS" if sys.platform == "win32" else "UNIX" self.server.channel.request( "setDebuggerProperty", { "skipSuspendOnBreakpointException": ("BaseException", ), "skipPrintBreakpointException": ("NameError", ), "multiThreadsSingleNotification": True, "ideOS": client_os_type, }, ) # Let the client know that it can begin configuring the adapter. self.channel.send_event("initialized") self.start_request = request return messaging.NO_RESPONSE # will respond on "configurationDone"
def handle(self, request): assert request.is_request("launch", "attach") if self._initialize_request is None: raise request.isnt_valid("Session is not initialized yet") if self.launcher or self.server: raise request.isnt_valid("Session is already started") self.session.no_debug = request("noDebug", json.default(False)) if self.session.no_debug: servers.dont_wait_for_first_connection() self.session.debug_options = debug_options = set( request("debugOptions", json.array(unicode)) ) f(self, request) if request.response is not None: return if self.server: self.server.initialize(self._initialize_request) self._initialize_request = None arguments = request.arguments if self.launcher: redirecting = arguments.get("console") == "internalConsole" if "RedirectOutput" in debug_options: # The launcher is doing output redirection, so we don't need the # server to do it, as well. arguments = dict(arguments) arguments["debugOptions"] = list( debug_options - {"RedirectOutput"} ) redirecting = True if arguments.get("redirectOutput"): arguments = dict(arguments) del arguments["redirectOutput"] redirecting = True arguments["isOutputRedirected"] = redirecting # pydevd doesn't send "initialized", and responds to the start request # immediately, without waiting for "configurationDone". If it changes # to conform to the DAP spec, we'll need to defer waiting for response. try: self.server.channel.request(request.command, arguments) except messaging.NoMoreMessages: # Server closed connection before we could receive the response to # "attach" or "launch" - this can happen when debuggee exits shortly # after starting. It's not an error, but we can't do anything useful # here at this point, either, so just bail out. request.respond({}) self.session.finalize( fmt( "{0} disconnected before responding to {1!j}", self.server, request.command, ) ) return except messaging.MessageHandlingError as exc: exc.propagate(request) if self.session.no_debug: self.start_request = request self.has_started = True request.respond({}) self._propagate_deferred_events() return if "clientOS" in request: client_os = request("clientOS", json.enum("windows", "unix")).upper() elif {"WindowsClient", "Windows"} & debug_options: client_os = "WINDOWS" elif {"UnixClient", "UNIX"} & debug_options: client_os = "UNIX" else: client_os = "WINDOWS" if sys.platform == "win32" else "UNIX" self.server.channel.request( "setDebuggerProperty", { "skipSuspendOnBreakpointException": ("BaseException",), "skipPrintBreakpointException": ("NameError",), "multiThreadsSingleNotification": True, "ideOS": client_os, }, ) # Let the client know that it can begin configuring the adapter. self.channel.send_event("initialized") self.start_request = request return messaging.NO_RESPONSE # will respond on "configurationDone"