Repository: microsoft/ptvsd

Issue:
Incorrect frame for breakpoint
## Environment data

- PTVSD version: today's master
- OS and version: Win10
- Python version (& distribution if applicable, e.g. Anaconda): 3.6
- Using VS Code or Visual Studio: Visual Studio

## Steps to reproduce:
1. Type this code and set breakpoint on line 9 as in the picture and start debugging.
```
import threading
from queue import Queue
import time

print_lock = threading.Lock()

def get_url(current_url):

    time.sleep(2)
    with print_lock:
        print("\nStarting thread {}".format(threading.current_thread().name))

def process_queue():
    while True:
        current_url = url_queue.get()
        get_url(current_url)
        url_queue.task_done()

url_queue = Queue()

url_list = ["https://www.google.com"]*5

for i in range(5):
    t = threading.Thread(target=process_queue)
    t.daemon = True
    t.start()

start = time.time()

for current_url in url_list:
    url_queue.put(current_url)

url_queue.join()

```

![image](https://user-images.githubusercontent.com/1696845/44122248-dba913c4-9fd7-11e8-9b16-2c36f9a3dfcc.png)

## Actual behavior

VS breaks on line 16.

## Expected  behavior

VS breaks on line 9.



Discussion:

Resolving patch:
diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py
--- a/ptvsd/wrapper.py
+++ b/ptvsd/wrapper.py
@@ -1019,6 +1019,12 @@ def __init__(self, socket,
 
         self._stopped = False
 
+        # These are needed to handle behavioral differences between VS and VSC
+        # https://github.com/Microsoft/VSDebugAdapterHost/wiki/Differences-between-Visual-Studio-Code-and-the-Visual-Studio-Debug-Adapter-Host # noqa
+        # VS expects a single stopped event in a multi-threaded scenario.
+        self._client_id = None
+        self._vs_ignore_stopped_events = threading.Event()
+
         # adapter state
         self.start_reason = None
         self.debug_options = {}
@@ -1081,6 +1087,7 @@ def handle_exiting(self, exitcode=None, wait=None):
 
     def on_initialize(self, request, args):
         # TODO: docstring
+        self._client_id = args.get('clientID', None)
         self._restart_debugger = False
         self.is_process_created = False
         self.send_response(request, **INITIALIZE_RESPONSE)
@@ -1104,6 +1111,7 @@ def on_launch(self, request, args):
     def on_configurationDone(self, request, args):
         # TODO: docstring
         debugger_attached.set()
+        self._vs_ignore_stopped_events.clear()
         self.send_response(request)
         self._process_debug_options(self.debug_options)
         self._handle_configurationDone(args)
@@ -2017,20 +2025,24 @@ def on_pause(self, request, args):
 
     @async_handler
     def on_continue(self, request, args):
+        self._vs_ignore_stopped_events.clear()
+
         # Always continue all threads.
         self.pydevd_notify(pydevd_comm.CMD_THREAD_RUN, '*')
         self.send_response(request, allThreadsContinued=True)
 
     @async_handler
     def on_next(self, request, args):
-        # TODO: docstring
+        self._vs_ignore_stopped_events.clear()
+
         tid = self.thread_map.to_pydevd(int(args['threadId']))
         self.pydevd_notify(pydevd_comm.CMD_STEP_OVER, tid)
         self.send_response(request)
 
     @async_handler
     def on_stepIn(self, request, args):
-        # TODO: docstring
+        self._vs_ignore_stopped_events.clear()
+
         tid = self.thread_map.to_pydevd(int(args['threadId']))
         if self._is_just_my_code_stepping_enabled():
             self.pydevd_notify(pydevd_comm.CMD_STEP_INTO_MY_CODE, tid)
@@ -2040,7 +2052,8 @@ def on_stepIn(self, request, args):
 
     @async_handler
     def on_stepOut(self, request, args):
-        # TODO: docstring
+        self._vs_ignore_stopped_events.clear()
+
         tid = self.thread_map.to_pydevd(int(args['threadId']))
         self.pydevd_notify(pydevd_comm.CMD_STEP_RETURN, tid)
         self.send_response(request)
@@ -2326,6 +2339,11 @@ def on_setDebuggerProperty(self, request, args):
 
         self.send_response(request)
 
+    def is_vs_client(self):
+        """ Return True if the client is 'Visual Studio' False otherwise
+        """
+        return self._client_id == 'visualstudio'
+
     # PyDevd protocol event handlers
 
     @pydevd_events.handler(pydevd_comm.CMD_INPUT_REQUESTED)
@@ -2434,6 +2452,13 @@ def on_pydevd_thread_suspend(self, seq, args):
             exc_name, exc_desc, _, _  = \
                 self._parse_exception_details(resp_args, include_stack=False)
 
+        if self.is_vs_client():
+            if self._vs_ignore_stopped_events.is_set() and reason == 'pause':
+                return
+            else:
+                extra['allThreadsStopped'] = True
+                self._vs_ignore_stopped_events.set()
+
         self.send_event(
             'stopped',
             reason=reason,


Test patch: