Repository: microsoft/ptvsd

Issue:
stdout output not displayed in terminal
## Environment data

- PTVSD version: Master
- OS and version: Mac OS (but could be on other OS)
- Python version (& distribution if applicable, e.g. Anaconda): N/A
- Using VS Code or Visual Studio:N/A

## Actual behavior

Data written into `stdout` not displayed in Terminal

## Expected  behavior

Output should be displayed before program exits

## Steps to reproduce:
1. Python program:
```python
import sys

sys.stdout.write("This will not be printed")
```
2. Debug the above program in a terminal window, and it will not be displayed in the Terminal.

Commit introducing the bug bd390322a8e31dcdb318da95e108c82f7f3a4f97




Discussion:
Assigning to @ericsnowcurrently as you're in a better position to fix this as your PR introduced this issue. 


Resolving patch:
diff --git a/ptvsd/daemon.py b/ptvsd/daemon.py
--- a/ptvsd/daemon.py
+++ b/ptvsd/daemon.py
@@ -5,7 +5,9 @@
 from ptvsd import wrapper
 from ptvsd.socket import (
     close_socket, create_server, create_client, connect, Address)
-from .exit_handlers import ExitHandlers, UnsupportedSignalError
+from .exit_handlers import (
+    ExitHandlers, UnsupportedSignalError,
+    kill_current_proc)
 from .session import PyDevdDebugSession
 from ._util import (
     ClosedError, NotRunningError, ignore_errors, debug, lock_wait)
@@ -82,7 +84,7 @@ def __init__(self, wait_for_user=_wait_for_user,
         self._wait_for_user = wait_for_user
         self._killonclose = killonclose
 
-        self._exiting = False
+        self._exiting_via_atexit_handler = False
 
         self._exithandlers = ExitHandlers()
         if addhandlers:
@@ -282,29 +284,42 @@ def _stop_quietly(self):
         with ignore_errors():
             self._stop()
 
-    def _kill_after_single_session(self):
-        if self._killonclose:
-            if not self._exiting:
-                # Ensure the proc is exiting before closing socket.
-                # Note that this will trigger the atexit handler.
-                self._exiting = True
-                sys.exit(0)
-        else:
-            try:
-                self.close()
-            except DaemonClosedError:
-                pass
-
     def _handle_session_disconnecting(self, session):
         debug('handling disconnecting session')
         if self._singlesession:
-            self._kill_after_single_session()
+            if self._killonclose:
+                with self._lock:
+                    if not self._exiting_via_atexit_handler:
+                        # Ensure the proc is exiting before closing
+                        # socket.  Note that we kill the proc instead
+                        # of calling sys.exit(0).
+                        # Note that this will trigger either the atexit
+                        # handler or the signal handler.
+                        kill_current_proc()
+            else:
+                try:
+                    self.close()
+                except DaemonClosedError:
+                    pass
 
     def _handle_session_closing(self, session):
         debug('handling closing session')
 
         if self._singlesession:
-            self._kill_after_single_session()
+            if self._killonclose:
+                with self._lock:
+                    if not self._exiting_via_atexit_handler:
+                        # Ensure the proc is exiting before closing
+                        # socket.  Note that we kill the proc instead
+                        # of calling sys.exit(0).
+                        # Note that this will trigger either the atexit
+                        # handler or the signal handler.
+                        kill_current_proc()
+            else:
+                try:
+                    self.close()
+                except DaemonClosedError:
+                    pass
         else:
             self._finish_session()
 
@@ -397,7 +412,8 @@ def _install_exit_handlers(self):
 
     def _handle_atexit(self):
         debug('handling atexit')
-        self._exiting = True
+        with self._lock:
+            self._exiting_via_atexit_handler = True
         session = self.session
 
         if session is not None:
@@ -428,8 +444,7 @@ def _handle_signal(self, signum, frame):
             self.close()
         except DaemonClosedError:
             pass
-        if not self._exiting:
-            self._exiting = True
+        if not self._exiting_via_atexit_handler:
             sys.exit(0)
 
     # methods for subclasses to override
diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py
--- a/ptvsd/wrapper.py
+++ b/ptvsd/wrapper.py
@@ -39,7 +39,6 @@
 import ptvsd.ipcjson as ipcjson  # noqa
 import ptvsd.futures as futures  # noqa
 import ptvsd.untangle as untangle  # noqa
-from ptvsd.exit_handlers import kill_current_proc
 from ptvsd.pathutils import PathUnNormcase  # noqa
 from ptvsd.safe_repr import SafeRepr  # noqa
 from ptvsd.version import __version__  # noqa
@@ -1008,9 +1007,6 @@ def __init__(self, socket,
 
         self._stopped = False
 
-        # This is overridden in tests.
-        self._kill_current_proc = kill_current_proc
-
         # adapter state
         self.start_reason = None
         self.debug_options = {}
@@ -1108,30 +1104,28 @@ def on_disconnect(self, request, args):
 
         status = {'sent': False}
 
-        def finish_disconnect():
+        def disconnect_response():
             if status['sent']:
                 return
             self.send_response(request)
             status['sent'] = True
 
-            if self.start_reason == 'launch':
-                if not self.closed:
-                    # Closing the socket causes pydevd to resume all
-                    # threads, so just terminate the process altogether.
-                    # TODO: The wrapper should not be responsible for
-                    # managing the process (e.g. killing it).
-                    self._kill_current_proc()
-
-            self._set_disconnected()
-            if self.start_reason == 'attach':
-                if not self._debuggerstopped:
-                    self._handle_detach()
         self._notify_disconnecting(
-            pre_socket_close=finish_disconnect,
+            pre_socket_close=disconnect_response,
         )
-        # The callback might never get called in notify_disconnecting,
-        # so we call it again here to make sure it is called.
-        finish_disconnect()
+        disconnect_response()
+
+        self._set_disconnected()
+
+        if self.start_reason == 'attach':
+            if not self._debuggerstopped:
+                self._handle_detach()
+        # TODO: We should be able drop the "launch" branch.
+        elif self.start_reason == 'launch':
+            if not self.closed:
+                # Closing the socket causes pydevd to resume all threads,
+                # so just terminate the process altogether.
+                sys.exit(0)
 
     # internal methods
 


Test patch:
diff --git a/tests/helpers/pydevd/_binder.py b/tests/helpers/pydevd/_binder.py
--- a/tests/helpers/pydevd/_binder.py
+++ b/tests/helpers/pydevd/_binder.py
@@ -30,8 +30,7 @@ def from_connect_func(cls, connect, singlesession=None):
             singlesession=singlesession,
         )
         self.start()
-        session = self.start_session(client, 'ptvsd.Server')
-        session._msgprocessor._kill_current_proc = (lambda: None)
+        self.start_session(client, 'ptvsd.Server')
         self.server = server
         return self