def __init__(self, sock): from ptvsd.adapter import sessions self.server = None """The Server component, if this debug server belongs to Session. """ self.pid = None stream = messaging.JsonIOStream.from_socket(sock, str(self)) self.channel = messaging.JsonMessageChannel(stream, self) self.channel.start() try: info = self.channel.request("pydevdSystemInfo") process_info = info("process", json.object()) self.pid = process_info("pid", int) self.ppid = process_info("ppid", int, optional=True) if self.ppid == (): self.ppid = None self.channel.name = stream.name = str(self) with _lock: if any(conn.pid == self.pid for conn in _connections): raise KeyError( fmt("{0} is already connected to this adapter", self)) _connections.append(self) _connections_changed.set() except Exception: log.exception("Failed to accept incoming server connection:") # If we couldn't retrieve all the necessary info from the debug server, # or there's a PID clash, we don't want to track this debuggee anymore, # but we want to continue accepting connections. self.channel.close() return parent_session = sessions.get(self.ppid) if parent_session is None: log.info("No active debug session for parent process of {0}.", self) else: try: parent_session.ide.notify_of_subprocess(self) except Exception: # This might fail if the IDE concurrently disconnects from the parent # session. We still want to keep the connection around, in case the # IDE reconnects later. If the parent session was "launch", it'll take # care of closing the remaining server connections. log.exception("Failed to notify parent session about {0}:", self)
def _process_request(self, request): self.timeline.record_request(request, block=False) if request.command == "runInTerminal": args = request("args", json.array(unicode)) cwd = request("cwd", ".") env = request("env", json.object(unicode)) try: exe = args.pop(0) assert not len(self.spawn_debuggee.env) self.spawn_debuggee.env = env self.spawn_debuggee(args, cwd, exe=exe) return {} except OSError as exc: log.exception('"runInTerminal" failed:') raise request.cant_handle(str(exc)) else: raise request.isnt_valid("not supported")
def launch_request(request): debug_options = set(request("debugOptions", json.array(unicode))) # Handling of properties that can also be specified as legacy "debugOptions" flags. # If property is explicitly set to false, but the flag is in "debugOptions", treat # it as an error. Returns None if the property wasn't explicitly set either way. def property_or_debug_option(prop_name, flag_name): assert prop_name[0].islower() and flag_name[0].isupper() value = request(prop_name, bool, optional=True) if value == (): value = None if flag_name in debug_options: if value is False: raise request.isnt_valid( '{0!j}:false and "debugOptions":[{1!j}] are mutually exclusive', prop_name, flag_name, ) value = True return value cmdline = [] if property_or_debug_option("sudo", "Sudo"): if sys.platform == "win32": raise request.cant_handle('"sudo":true is not supported on Windows.') else: cmdline += ["sudo"] # "pythonPath" is a deprecated legacy spelling. If "python" is missing, then try # the alternative. But if both are missing, the error message should say "python". python_key = "python" if python_key in request: if "pythonPath" in request: raise request.isnt_valid( '"pythonPath" is not valid if "python" is specified' ) elif "pythonPath" in request: python_key = "pythonPath" python = request(python_key, json.array(unicode, vectorize=True, size=(1,))) if not len(python): python = [compat.filename(sys.executable)] cmdline += python if not request("noDebug", json.default(False)): port = request("port", int) cmdline += [ compat.filename(os.path.dirname(ptvsd.__file__)), "--client", "--host", "127.0.0.1", "--port", str(port), ] client_access_token = request("clientAccessToken", unicode, optional=True) if client_access_token != (): cmdline += ["--client-access-token", compat.filename(client_access_token)] ptvsd_args = request("ptvsdArgs", json.array(unicode)) cmdline += ptvsd_args program = module = code = () if "program" in request: program = request("program", json.array(unicode, vectorize=True, size=(1,))) cmdline += program process_name = program[0] if "module" in request: module = request("module", json.array(unicode, vectorize=True, size=(1,))) cmdline += ["-m"] + module process_name = module[0] if "code" in request: code = request("code", json.array(unicode, vectorize=True, size=(1,))) cmdline += ["-c"] + code process_name = python[0] num_targets = len([x for x in (program, module, code) if x != ()]) if num_targets == 0: raise request.isnt_valid( 'either "program", "module", or "code" must be specified' ) elif num_targets != 1: raise request.isnt_valid( '"program", "module", and "code" are mutually exclusive' ) cmdline += request("args", json.array(unicode)) cwd = request("cwd", unicode, optional=True) if cwd == (): # If it's not specified, but we're launching a file rather than a module, # and the specified path has a directory in it, use that. cwd = None if program == () else (os.path.dirname(program[0]) or None) env = os.environ.copy() if "PTVSD_TEST" in env: # If we're running as part of a ptvsd test, make sure that codecov is not # applied to the debuggee, since it will conflict with pydevd. env.pop("COV_CORE_SOURCE", None) env.update(request("env", json.object(unicode))) if request("gevent", False): env["GEVENT_SUPPORT"] = "True" redirect_output = property_or_debug_option("redirectOutput", "RedirectOutput") if redirect_output is None: # If neither the property nor the option were specified explicitly, choose # the default depending on console type - "internalConsole" needs it to # provide any output at all, but it's unnecessary for the terminals. redirect_output = request("console", unicode) == "internalConsole" if redirect_output: # sys.stdout buffering must be disabled - otherwise we won't see the output # at all until the buffer fills up. env["PYTHONUNBUFFERED"] = "1" # Force UTF-8 output to minimize data loss due to re-encoding. env["PYTHONIOENCODING"] = "utf-8" if property_or_debug_option("waitOnNormalExit", "WaitOnNormalExit"): debuggee.wait_on_exit_predicates.append(lambda code: code == 0) if property_or_debug_option("waitOnAbnormalExit", "WaitOnAbnormalExit"): debuggee.wait_on_exit_predicates.append(lambda code: code != 0) if sys.version_info < (3,): # Popen() expects command line and environment to be bytes, not Unicode. # Assume that values are filenames - it's usually either that, or numbers - # but don't allow encoding to fail if we guessed wrong. encode = functools.partial(compat.filename_bytes, errors="replace") cmdline = [encode(s) for s in cmdline] env = {encode(k): encode(v) for k, v in env.items()} debuggee.spawn(process_name, cmdline, cwd, env, redirect_output) return {}
def __init__(self, sock): from ptvsd.adapter import sessions self.disconnected = False self.server = None """The Server component, if this debug server belongs to Session. """ self.pid = None stream = messaging.JsonIOStream.from_socket(sock, str(self)) self.channel = messaging.JsonMessageChannel(stream, self) self.channel.start() try: self.authenticate() info = self.channel.request("pydevdSystemInfo") process_info = info("process", json.object()) self.pid = process_info("pid", int) self.ppid = process_info("ppid", int, optional=True) if self.ppid == (): self.ppid = None self.channel.name = stream.name = str(self) ptvsd_dir = os.path.dirname(os.path.dirname(ptvsd.__file__)) # Note: we must check if 'ptvsd' is not already in sys.modules because the # evaluation of an import at the wrong time could deadlock Python due to # its import lock. # # So, in general this evaluation shouldn't do anything. It's only # important when pydevd attaches automatically to a subprocess. In this # case, we have to make sure that ptvsd is properly put back in the game # for users to be able to use it.v # # In this case (when the import is needed), this evaluation *must* be done # before the configurationDone request is sent -- if this is not respected # it's possible that pydevd already started secondary threads to handle # commands, in which case it's very likely that this command would be # evaluated at the wrong thread and the import could potentially deadlock # the program. # # Note 2: the sys module is guaranteed to be in the frame globals and # doesn't need to be imported. inject_ptvsd = """ if 'ptvsd' not in sys.modules: sys.path.insert(0, {ptvsd_dir!r}) try: import ptvsd finally: del sys.path[0] """ inject_ptvsd = fmt(inject_ptvsd, ptvsd_dir=ptvsd_dir) try: self.channel.request("evaluate", {"expression": inject_ptvsd}) except messaging.MessageHandlingError: # Failure to inject is not a fatal error - such a subprocess can # still be debugged, it just won't support "import ptvsd" in user # code - so don't terminate the session. log.exception("Failed to inject ptvsd into {0}:", self, level="warning") with _lock: # The server can disconnect concurrently before we get here, e.g. if # it was force-killed. If the disconnect() handler has already run, # don't register this server or report it, since there's nothing to # deregister it. if self.disconnected: return if any(conn.pid == self.pid for conn in _connections): raise KeyError( fmt("{0} is already connected to this adapter", self)) _connections.append(self) _connections_changed.set() except Exception: log.exception("Failed to accept incoming server connection:") self.channel.close() # If this was the first server to connect, and the main thread is inside # wait_until_disconnected(), we want to unblock it and allow it to exit. dont_wait_for_first_connection() # If we couldn't retrieve all the necessary info from the debug server, # or there's a PID clash, we don't want to track this debuggee anymore, # but we want to continue accepting connections. return parent_session = sessions.get(self.ppid) if parent_session is None: log.info("No active debug session for parent process of {0}.", self) else: try: parent_session.ide.notify_of_subprocess(self) except Exception: # This might fail if the IDE concurrently disconnects from the parent # session. We still want to keep the connection around, in case the # IDE reconnects later. If the parent session was "launch", it'll take # care of closing the remaining server connections. log.exception("Failed to notify parent session about {0}:", self)