def notify_root(port): assert options.subprocess_of debug('Subprocess %d notifying root process at port %d' % (os.getpid(), options.subprocess_notify)) conn = create_client() conn.connect(('localhost', options.subprocess_notify)) stream = JsonIOStream.from_socket(conn) channel = JsonMessageChannel(stream) channel.start() # Send the notification about ourselves to root, and wait for it to tell us # whether an incoming connection is anticipated. This will be true if root # had successfully propagated the notification to the IDE, and false if it # couldn't do so (e.g. because the IDE is not attached). There's also the # possibility that connection to root will just drop, e.g. if it crashes - # in that case, just exit immediately. request = channel.send_request( 'ptvsd_subprocess', { 'parentProcessId': options.subprocess_of, 'processId': os.getpid(), 'port': port, }) try: response = request.wait_for_response() except Exception: print('Failed to send subprocess notification; exiting', file=sys.__stderr__) traceback.print_exc() sys.exit(0) # Keep the channel open until we exit - root process uses open channels to keep # track of which subprocesses are alive and which are not. atexit.register(lambda: channel.close()) if not response['incomingConnection']: debugger = get_global_debugger() while debugger is None: time.sleep(0.1) debugger = get_global_debugger() debugger.ready_to_run = True
def notify_root(port): assert options.subprocess_of debug('Subprocess %d notifying root process at port %d' % (os.getpid(), options.subprocess_notify)) conn = create_client() conn.connect(('localhost', options.subprocess_notify)) stream = JsonIOStream.from_socket(conn) channel = JsonMessageChannel(stream) channel.start() # Send the notification about ourselves to root, and wait for it to tell us # whether an incoming connection is anticipated. This will be true if root # had successfully propagated the notification to the IDE, and false if it # couldn't do so (e.g. because the IDE is not attached). There's also the # possibility that connection to root will just drop, e.g. if it crashes - # in that case, just exit immediately. request = channel.send_request('ptvsd_subprocess', { 'parentProcessId': options.subprocess_of, 'processId': os.getpid(), 'port': port, }) try: response = request.wait_for_response() except Exception: print('Failed to send subprocess notification; exiting', file=sys.__stderr__) traceback.print_exc() sys.exit(0) # Keep the channel open until we exit - root process uses open channels to keep # track of which subprocesses are alive and which are not. atexit.register(lambda: channel.close()) if not response['incomingConnection']: debugger = get_global_debugger() while debugger is None: time.sleep(0.1) debugger = get_global_debugger() debugger.ready_to_run = True
class DebugSession(object): WAIT_FOR_EXIT_TIMEOUT = 5 def __init__(self, method='launch', ptvsd_port=None): assert method in ('launch', 'attach_pid', 'attach_socket') assert ptvsd_port is None or method == 'attach_socket' print('New debug session with method %r' % method) self.method = method self.ptvsd_port = ptvsd_port self.process = None self.socket = None self.server_socket = None self.connected = threading.Event() self.timeline = Timeline() def stop(self): if self.process: try: self.process.kill() except: pass if self.socket: try: self.socket.shutdown(socket.SHUT_RDWR) except: self.socket = None if self.server_socket: try: self.server_socket.shutdown(socket.SHUT_RDWR) except: self.server_socket = None def prepare_to_run(self, perform_handshake=True, filename=None, module=None): """Spawns ptvsd using the configured method, telling it to execute the provided Python file or module, and establishes a message channel to it. If perform_handshake is True, calls self.handshake() before returning. """ argv = [sys.executable] if self.method != 'attach_pid': argv += ['-m', 'ptvsd'] if self.method == 'attach_socket': if self.ptvsd_port is None: self.ptvsd_port = 5678 argv += ['--port', str(self.ptvsd_port)] else: port = self._listen() argv += ['--host', 'localhost', '--port', str(port)] if filename: assert not module argv += [filename] elif module: assert not filename argv += ['-m', module] env = os.environ.copy() env.update({'PYTHONPATH': PTVSD_SYS_PATH}) print('Spawning %r' % argv) self.process = subprocess.Popen(argv, env=env, stdin=subprocess.PIPE) if self.ptvsd_port: # ptvsd will take some time to spawn and start listening on the port, # so just hammer at it until it responds (or we time out). while not self.socket: try: self._connect() except socket.error: pass time.sleep(0.1) else: self.connected.wait() assert self.socket self.stream = LoggingJsonStream(JsonIOStream.from_socket(self.socket)) handlers = MessageHandlers(request=self._process_request, event=self._process_event) self.channel = JsonMessageChannel(self.stream, handlers) self.channel.start() if perform_handshake: self.handshake() def wait_for_exit(self, expected_returncode=0): """Waits for the spawned ptvsd process to exit. If it doesn't exit within WAIT_FOR_EXIT_TIMEOUT seconds, forcibly kills the process. After the process exits, validates its return code to match expected_returncode. """ process = self.process if not process: return def kill(): time.sleep(self.WAIT_FOR_EXIT_TIMEOUT) print('ptvsd process timed out, killing it') p = process if p: p.kill() kill_thread = threading.Thread(target=kill) kill_thread.daemon = True kill_thread.start() process.wait() assert process.returncode == expected_returncode def _listen(self): self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server_socket.bind(('localhost', 0)) _, port = self.server_socket.getsockname() self.server_socket.listen(0) def accept_worker(): print('Listening for incoming connection from ptvsd on port %d' % port) self.socket, _ = self.server_socket.accept() print('Incoming ptvsd connection accepted') self.connected.set() accept_thread = threading.Thread(target=accept_worker) accept_thread.daemon = True accept_thread.start() return port def _connect(self): print('Trying to connect to ptvsd on port %d' % self.ptvsd_port) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost', self.ptvsd_port)) print('Successfully connected to ptvsd') self.socket = sock def send_request(self, command, arguments=None): request = self.timeline.record_request(command, arguments) request.sent = self.channel.send_request(command, arguments) request.sent.on_response( lambda response: self._process_response(request, response)) assert Request(command, arguments) in self.timeline return request def mark(self, id): return self.timeline.mark(id) @contextlib.contextmanager def causing(self, expectation): assert expectation not in self.timeline promised_occurrence = ['ONLY VALID AFTER END OF BLOCK!'] yield promised_occurrence occ = self.wait_until(expectation) promised_occurrence[:] = (occ, ) def handshake(self): """Performs the handshake that establishes the debug session. After this method returns, ptvsd is not running any code yet, but it is ready to accept any configuration requests (e.g. for initial breakpoints). Once initial configuration is complete, start_debugging() should be called to finalize the configuration stage, and start running code. """ with self.causing(Event('initialized', {})): self.send_request('initialize', { 'adapterID': 'test' }).wait_for_response() def start_debugging(self, arguments=None, force_threads=True): """Finalizes the configuration stage, and issues a 'launch' or an 'attach' request to start running code under debugger, passing arguments through. After this method returns, ptvsd is running the code in the script file or module that was specified in run(). """ request = 'launch' if self.method == 'launch' else 'attach' self.send_request(request, arguments).wait_for_response() if force_threads: self.send_request('threads').wait_for_response() self.send_request('configurationDone').wait_for_response() def _process_event(self, channel, event, body): self.timeline.record_event(event, body) if event == 'terminated': self.channel.close() def _process_response(self, request, response): body = response.body if response.success else RequestFailure( response.error_message) self.timeline.record_response(request, body) assert Response(request, body) in self.timeline def _process_request(self, channel, command, arguments): assert False, 'ptvsd should not be sending requests.' def wait_until(self, expectation): return self.timeline.wait_until(expectation) def history(self): return self.timeline.history