From 4476d0da6f2dde846d89700480cc534701b70d07 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 27 Jun 2018 14:25:48 -0600 Subject: [PATCH] Send the disconnect response before closing the client socket. (#531) (see #530) We were sending the "disconnect" response only after closing the socket, meaning the response was never actually sent. This PR fixes that by ensuring the response is sent as late as possible, whether or not it is a "single session" situation. --- ptvsd/session.py | 7 ++++++- ptvsd/wrapper.py | 28 +++++++++++++++++++++++----- tests/system_tests/test_main.py | 1 + 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/ptvsd/session.py b/ptvsd/session.py index 914d5e52..217490bc 100644 --- a/ptvsd/session.py +++ b/ptvsd/session.py @@ -47,12 +47,16 @@ class DebugSession(Startable, Closeable): self._notify_disconnecting = notify_disconnecting self._sock = sock + self._pre_socket_close = None if ownsock: # Close the socket *after* calling sys.exit() (via notify_closing). def handle_closing(before): if before: return + debug('closing session socket') proc = self._msgprocessor + if self._pre_socket_close is not None: + self._pre_socket_close() if proc is not None: try: proc.wait_while_connected(10) # seconds @@ -134,8 +138,9 @@ class DebugSession(Startable, Closeable): # internal methods for VSCodeMessageProcessor - def _handle_vsc_disconnect(self): + def _handle_vsc_disconnect(self, pre_socket_close=None): debug('disconnecting') + self._pre_socket_close = pre_socket_close # TODO: Fail if already set? self._notify_disconnecting(self) def _handle_vsc_close(self): diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py index aa522c13..39acb888 100644 --- a/ptvsd/wrapper.py +++ b/ptvsd/wrapper.py @@ -811,6 +811,7 @@ class VSCodeMessageProcessorBase(ipcjson.SocketIO, ipcjson.IpcChannel): self._notify_closing = notify_closing self.server_thread = None + self._closing = False self._closed = False self.readylock = threading.Lock() self.readylock.acquire() # Unlock at the end of start(). @@ -824,6 +825,10 @@ class VSCodeMessageProcessorBase(ipcjson.SocketIO, ipcjson.IpcChannel): with self._connlock: return _util.is_locked(self._connected) + @property + def closed(self): + return self._closed or self._closing + @property def listening(self): # TODO: must be disconnected? @@ -882,9 +887,9 @@ class VSCodeMessageProcessorBase(ipcjson.SocketIO, ipcjson.IpcChannel): def close(self): """Stop the message processor and release its resources.""" - if self._closed: + if self.closed: return - self._closed = True + self._closing = True debug('raw closing') self._notify_closing() @@ -895,6 +900,7 @@ class VSCodeMessageProcessorBase(ipcjson.SocketIO, ipcjson.IpcChannel): with self._connlock: _util.lock_release(self._listening) _util.lock_release(self._connected) + self._closed = True # VSC protocol handlers @@ -1080,8 +1086,20 @@ class VSCLifecycleMsgProcessor(VSCodeMessageProcessorBase): # TODO: docstring if self._debuggerstopped: # A "terminated" event must have been sent. self._wait_until_exiting(self.EXITWAIT) - self._notify_disconnecting() - self.send_response(request) + + status = {'sent': False} + + def disconnect_response(): + if status['sent']: + return + self.send_response(request) + status['sent'] = True + + self._notify_disconnecting( + pre_socket_close=disconnect_response, + ) + disconnect_response() + self._set_disconnected() if self.start_reason == 'attach': @@ -1089,7 +1107,7 @@ class VSCLifecycleMsgProcessor(VSCodeMessageProcessorBase): self._handle_detach() # TODO: We should be able drop the "launch" branch. elif self.start_reason == 'launch': - if not self._closed: + if not self.closed: # Closing the socket causes pydevd to resume all threads, # so just terminate the process altogether. sys.exit(0) diff --git a/tests/system_tests/test_main.py b/tests/system_tests/test_main.py index 4843e73c..52d804d2 100644 --- a/tests/system_tests/test_main.py +++ b/tests/system_tests/test_main.py @@ -512,6 +512,7 @@ class LifecycleTests(TestsBase, unittest.TestCase): done2, waitscript2 = lockfile2.wait_in_script(timeout=5) filename = self.write_script('spam.py', waitscript1 + waitscript2) addr = Address('localhost', 8888) + #DebugAdapter.VERBOSE = True with DebugAdapter.start_for_attach(addr, filename) as adapter: with DebugClient() as editor: # Attach initially.