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.