diff --git a/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py b/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py index 215b8a96d..594f1710b 100644 --- a/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py +++ b/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py @@ -191,6 +191,7 @@ def unquote(s): CMD_VERSION = 501 CMD_RETURN = 502 +CMD_SET_PROTOCOL = 503 CMD_ERROR = 901 ID_TO_MEANING = { @@ -257,6 +258,7 @@ def unquote(s): '501': 'CMD_VERSION', '502': 'CMD_RETURN', + '503': 'CMD_SET_PROTOCOL', '901': 'CMD_ERROR', } @@ -449,16 +451,11 @@ def _on_run(self): """ just loop and write responses """ self._stop_trace() - get_has_timeout = sys.hexversion >= 0x02030000 # 2.3 onwards have it. try: while True: try: try: - if get_has_timeout: - cmd = self.cmdQueue.get(1, 0.1) - else: - time.sleep(.01) - cmd = self.cmdQueue.get(0) + cmd = self.cmdQueue.get(1, 0.1) except _queue.Empty: if self.killReceived: try: @@ -475,21 +472,8 @@ def _on_run(self): #when liberating the thread here, we could have errors because we were shutting down #but the thread was still not liberated return - out = cmd.outgoing - - if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: - out_message = 'sending cmd --> ' - out_message += "%20s" % ID_TO_MEANING.get(out[:3], 'UNKNOWN') - out_message += ' ' - out_message += unquote(unquote(out)).replace('\n', ' ') - try: - sys.stderr.write('%s\n' % (out_message,)) - except: - pass - - if IS_PY3K: - out = bytearray(out, 'utf-8') - self.sock.send(out) #TODO: this does not guarantee that all message are sent (and jython does not have a send all) + cmd.send(self.sock) + if cmd.id == CMD_EXIT: break if time is None: @@ -499,7 +483,7 @@ def _on_run(self): GlobalDebuggerHolder.global_dbg.finish_debugging_session() if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 0: traceback.print_exc() - + def empty(self): return self.cmdQueue.empty() @@ -587,19 +571,88 @@ class NetCommand: or one to be sent by daemon. """ next_seq = 0 # sequence numbers - - def __init__(self, id, seq, text): - """ smart handling of parameters - if sequence is 0, new sequence will be generated - if text has carriage returns they'll be replaced""" - self.id = id + + # Protocol where each line is a new message (text is quoted to prevent new lines). + QUOTED_LINE_PROTOCOL = 'quoted-line' + + # Uses http protocol to provide a new message. + # i.e.: Content-Length:xxx\r\n\r\npayload + HTTP_PROTOCOL = 'http' + + protocol = QUOTED_LINE_PROTOCOL + + _showing_debug_info = 0 + _show_debug_info_lock = threading.RLock() + + def __init__(self, cmd_id, seq, text): + """ + If sequence is 0, new sequence will be generated (otherwise, this was the response + to a command from the client). + """ + self.id = cmd_id if seq == 0: NetCommand.next_seq += 2 seq = NetCommand.next_seq self.seq = seq - self.text = text - encoded = quote(to_string(text), '/<>_=" \t') - self.outgoing = '%s\t%s\t%s\n' % (id, seq, encoded) + + if IS_PY2: + if isinstance(text, unicode): + text = text.encode('utf-8') + else: + assert isinstance(text, str) + else: + assert isinstance(text, str) + + if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: + self._show_debug_info(cmd_id, seq, text) + + if self.protocol == self.HTTP_PROTOCOL: + msg = '%s\t%s\t%s\n' % (cmd_id, seq, text) + else: + encoded = quote(to_string(text), '/<>_=" \t') + msg = '%s\t%s\t%s\n' % (cmd_id, seq, encoded) + + + if IS_PY2: + assert isinstance(msg, str) # i.e.: bytes + as_bytes = msg + else: + if isinstance(msg, str): + msg = msg.encode('utf-8') + + assert isinstance(msg, bytes) + as_bytes = msg + self._as_bytes = as_bytes + + def send(self, sock): + as_bytes = self._as_bytes + if self.protocol == self.HTTP_PROTOCOL: + sock.sendall(('Content-Length: %s\r\n\r\n' % len(as_bytes)).encode('ascii')) + + sock.sendall(as_bytes) + + @classmethod + def _show_debug_info(cls, cmd_id, seq, text): + with cls._show_debug_info_lock: + # Only one thread each time (rlock). + if cls._showing_debug_info: + # avoid recursing in the same thread (just printing could create + # a new command when redirecting output). + return + + cls._showing_debug_info += 1 + try: + out_message = 'sending cmd --> ' + out_message += "%20s" % ID_TO_MEANING.get(str(cmd_id), 'UNKNOWN') + out_message += ' ' + out_message += text.replace('\n', ' ') + try: + sys.stderr.write('%s\n' % (out_message,)) + except: + pass + finally: + cls._showing_debug_info -= 1 + #======================================================================================================================= # NetCommandFactory @@ -617,6 +670,9 @@ def make_error_message(self, seq, text): if DebugInfoHolder.DEBUG_TRACE_LEVEL > 2: sys.stderr.write("Error: %s" % (text,)) return cmd + + def make_protocol_set_message(self, seq): + return NetCommand(CMD_SET_PROTOCOL, seq, '') def make_thread_created_message(self, thread): cmdText = "" + self._thread_to_xml(thread) + "" diff --git a/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py b/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py index 0d4084681..d9dfbe2e6 100644 --- a/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py +++ b/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py @@ -20,7 +20,8 @@ CMD_RUN_CUSTOM_OPERATION, InternalRunCustomOperation, CMD_IGNORE_THROWN_EXCEPTION_AT, CMD_ENABLE_DONT_TRACE, \ CMD_SHOW_RETURN_VALUES, ID_TO_MEANING, CMD_GET_DESCRIPTION, InternalGetDescription, InternalLoadFullValue, \ CMD_LOAD_FULL_VALUE, CMD_REDIRECT_OUTPUT, CMD_GET_NEXT_STATEMENT_TARGETS, InternalGetNextStatementTargets, CMD_SET_PROJECT_ROOTS, \ - CMD_GET_THREAD_STACK, CMD_THREAD_DUMP_TO_STDERR, CMD_STOP_ON_START, CMD_GET_EXCEPTION_DETAILS + CMD_GET_THREAD_STACK, CMD_THREAD_DUMP_TO_STDERR, CMD_STOP_ON_START, CMD_GET_EXCEPTION_DETAILS, NetCommand,\ + CMD_SET_PROTOCOL from _pydevd_bundle.pydevd_constants import get_thread_id, IS_PY3K, DebugInfoHolder, dict_keys, STATE_RUN, \ NEXT_VALUE_SEPARATOR, IS_WINDOWS from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info @@ -49,10 +50,19 @@ def process_net_command(py_db, cmd_id, seq, text): if cmd_id == CMD_RUN: py_db.ready_to_run = True + elif cmd_id == CMD_SET_PROTOCOL: + expected = (NetCommand.HTTP_PROTOCOL, NetCommand.QUOTED_LINE_PROTOCOL) + text = text.strip() + assert text.strip() in expected, 'Protocol (%s) should be one of: %s' % ( + text, expected) + + NetCommand.protocol = text + cmd = py_db.cmd_factory.make_protocol_set_message(seq) + elif cmd_id == CMD_VERSION: # response is version number # ide_os should be 'WINDOWS' or 'UNIX'. - + # Default based on server process (although ideally the IDE should # provide it). if IS_WINDOWS: @@ -88,7 +98,7 @@ def process_net_command(py_db, cmd_id, seq, text): elif cmd_id == CMD_GET_THREAD_STACK: thread_id = text - + t = pydevd_find_thread_by_id(thread_id) frame = None if t and not getattr(t, 'pydev_do_not_trace', None): @@ -441,7 +451,7 @@ def process_net_command(py_db, cmd_id, seq, text): elif cmd_id == CMD_SET_PY_EXCEPTION: # Command which receives set of exceptions on which user wants to break the debugger - # text is: + # text is: # # break_on_uncaught; # break_on_caught; @@ -557,7 +567,7 @@ def process_net_command(py_db, cmd_id, seq, text): # notify_on_handled_exceptions can be 0, 1 or 2 # 0 means we should not stop on handled exceptions. # 1 means we should stop on handled exceptions showing it on all frames where the exception passes. - # 2 means we should stop on handled exceptions but we should only notify about it once. + # 2 means we should stop on handled exceptions but we should only notify about it once. # # To ignore_libraries properly, besides setting ignore_libraries to 1, the IDE_PROJECT_ROOTS environment # variable must be set (so, we'll ignore anything not below IDE_PROJECT_ROOTS) -- this is not ideal as @@ -784,16 +794,16 @@ def process_net_command(py_db, cmd_id, seq, text): int_cmd = InternalGetNextStatementTargets(seq, thread_id, frame_id) py_db.post_internal_command(int_cmd, thread_id) - + elif cmd_id == CMD_SET_PROJECT_ROOTS: pydevd_utils.set_project_roots(text.split(u'\t')) elif cmd_id == CMD_THREAD_DUMP_TO_STDERR: pydevd_utils.dump_threads() - + elif cmd_id == CMD_STOP_ON_START: py_db.stop_on_start = text.strip() in ('True', 'true', '1') - + elif cmd_id == CMD_GET_EXCEPTION_DETAILS: thread_id = text t = pydevd_find_thread_by_id(thread_id) @@ -806,7 +816,7 @@ def process_net_command(py_db, cmd_id, seq, text): finally: frame = None t = None - + else: #I have no idea what this is all about cmd = py_db.cmd_factory.make_error_message(seq, "unexpected command " + str(cmd_id)) diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py index 95611b5de..c95654c6b 100644 --- a/ptvsd/wrapper.py +++ b/ptvsd/wrapper.py @@ -391,6 +391,8 @@ def send(self, data): loop.call_soon_threadsafe(fut.set_result, (cmd_id, seq, args)) return result + sendall = send + def makefile(self, *args, **kwargs): """Return a file-like wrapper around the socket.""" return os.fdopen(self.pipe_r)