Repository: microsoft/debugpy

Issue:
Test failure: AttributeError: 'NoneType' object has no attribute 'additional_info'
Looks like some of the earlier issues we had - probably needs another null check?
```
2022-11-16T22:29:19.8770109Z 0.00s - PyDB.do_wait_suspend
2022-11-16T22:29:19.8770889Z name: _thread1 (line: 17)
2022-11-16T22:29:19.8771735Z  file: /tmp/pytest-of-vsts/pytest-0/popen-gw5/test_step_multi_threads_progra2/code_to_debug.py
2022-11-16T22:29:19.8772367Z  event: line
2022-11-16T22:29:19.8772744Z  arg: None
2022-11-16T22:29:19.8773420Z  step: CMD_STEP_INTO (original step: <Unknown: -1>)
2022-11-16T22:29:19.8774803Z  thread: <_DummyThread(Dummy-6, started daemon 139635158152960)>, thread id: pid_47755_id_139635499782096, id(thread): 139635499782096
2022-11-16T22:29:19.8776129Z 0.00s -   Stack: /tmp/pytest-of-vsts/pytest-0/popen-gw5/test_step_multi_threads_progra2/code_to_debug.py, _thread1, 17
2022-11-16T22:29:19.8777140Z 0.00s -   Stack: /opt/hostedtoolcache/Python/3.7.15/x64/lib/python3.7/threading.py, run, 870
2022-11-16T22:29:19.8778099Z 0.00s -   Stack: /opt/hostedtoolcache/Python/3.7.15/x64/lib/python3.7/threading.py, _bootstrap_inner, 926
2022-11-16T22:29:19.8779112Z 0.00s -   Stack: /opt/hostedtoolcache/Python/3.7.15/x64/lib/python3.7/threading.py, _bootstrap, 890
2022-11-16T22:29:19.8780208Z 0.00s -   Stack: /home/vsts/work/1/s/.tox/py37/lib/python3.7/site-packages/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py, __call__, 1070
2022-11-16T22:29:19.8781107Z 0.00s - Sending suspend notification.
2022-11-16T22:29:19.8781913Z 0.00s - Could not find thread pid_47755_id_139635499782096.
2022-11-16T22:29:19.8782919Z 0.00s - Available: (['pid_47755_id_139635538572624', 'pid_47755_id_139635516896016', 'pid_47755_id_139635516896528'],).
2022-11-16T22:29:19.8784589Z 0.00s - sending cmd (http_json) --> CMD_WRITE_TO_CONSOLE {"type": "event", "event": "output", "body": {"output": "<class 'AttributeError'> raised from within the callback set in sys.settrace.\nDebugging will be disabled for this thread (<_DummyThread(Dummy-6, started daemon 139635158152960)>).\n", "category": "console", "source": {}}, "seq": 26, "pydevd_cmd_id": 116}
2022-11-16T22:29:19.8785709Z 
2022-11-16T22:29:19.8786148Z Traceback (most recent call last):
2022-11-16T22:29:19.8786748Z   File "_pydevd_bundle/pydevd_cython.pyx", line 147, in _pydevd_bundle.pydevd_cython.set_additional_thread_info
2022-11-16T22:29:19.8787662Z AttributeError: 'NoneType' object has no attribute 'additional_info'
2022-11-16T22:29:19.8788154Z 
2022-11-16T22:29:19.8788645Z During handling of the above exception, another exception occurred:
2022-11-16T22:29:19.8788967Z 
2022-11-16T22:29:19.8789350Z Traceback (most recent call last):
2022-11-16T22:29:19.8789919Z   File "_pydevd_bundle/pydevd_cython.pyx", line 1135, in _pydevd_bundle.pydevd_cython.PyDBFrame.trace_dispatch
2022-11-16T22:29:19.8790636Z   File "_pydevd_bundle/pydevd_cython.pyx", line 312, in _pydevd_bundle.pydevd_cython.PyDBFrame.do_wait_suspend
2022-11-16T22:29:19.8791829Z   File "/home/vsts/work/1/s/.tox/py37/lib/python3.7/site-packages/debugpy/_vendored/pydevd/pydevd.py", line 2066, in do_wait_suspend
2022-11-16T22:29:19.8792686Z     with self._threads_suspended_single_notification.notify_thread_suspended(thread_id, stop_reason):
2022-11-16T22:29:19.8793782Z   File "/opt/hostedtoolcache/Python/3.7.15/x64/lib/python3.7/contextlib.py", line 112, in __enter__
2022-11-16T22:29:19.8794347Z     return next(self.gen)
2022-11-16T22:29:19.8795297Z   File "/home/vsts/work/1/s/.tox/py37/lib/python3.7/site-packages/debugpy/_vendored/pydevd/pydevd.py", line 451, in notify_thread_suspended
2022-11-16T22:29:19.8796163Z     with AbstractSingleNotificationBehavior.notify_thread_suspended(self, thread_id, stop_reason):
2022-11-16T22:29:19.8797033Z   File "/opt/hostedtoolcache/Python/3.7.15/x64/lib/python3.7/contextlib.py", line 112, in __enter__
2022-11-16T22:29:19.8797572Z     return next(self.gen)
2022-11-16T22:29:19.8798421Z   File "/home/vsts/work/1/s/.tox/py37/lib/python3.7/site-packages/debugpy/_vendored/pydevd/pydevd.py", line 405, in notify_thread_suspended
2022-11-16T22:29:19.8799146Z     self.on_thread_suspend(thread_id, stop_reason)
2022-11-16T22:29:19.8800066Z   File "/home/vsts/work/1/s/.tox/py37/lib/python3.7/site-packages/debugpy/_vendored/pydevd/pydevd.py", line 376, in on_thread_suspend
2022-11-16T22:29:19.8800783Z     self.send_suspend_notification(thread_id, stop_reason)
2022-11-16T22:29:19.8801992Z   File "/home/vsts/work/1/s/.tox/py37/lib/python3.7/site-packages/debugpy/_vendored/pydevd/pydevd.py", line 445, in send_suspend_notification
2022-11-16T22:29:19.8802849Z     py_db.writer.add_command(py_db.cmd_factory.make_thread_suspend_single_notification(py_db, thread_id, stop_reason))
2022-11-16T22:29:19.8804568Z   File "/home/vsts/work/1/s/.tox/py37/lib/python3.7/site-packages/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py", line 353, in make_thread_suspend_single_notification
2022-11-16T22:29:19.8805464Z     info = set_additional_thread_info(thread)
2022-11-16T22:29:19.8806120Z   File "_pydevd_bundle/pydevd_cython.pyx", line 151, in _pydevd_bundle.pydevd_cython.set_additional_thread_info
2022-11-16T22:29:19.8807075Z   File "_pydevd_bundle/pydevd_cython.pyx", line 157, in _pydevd_bundle.pydevd_cython.set_additional_thread_info
2022-11-16T22:29:19.8808024Z AttributeError: 'NoneType' object has no attribute 'additional_info'
2022-11-16T22:29:19.8808635Z Traceback (most recent call last):
2022-11-16T22:29:19.8809445Z   File "_pydevd_bundle/pydevd_cython.pyx", line 147, in _pydevd_bundle.pydevd_cython.set_additional_thread_info
2022-11-16T22:29:19.8810867Z AttributeError: 'NoneType' object has no attribute 'additional_info'
2022-11-16T22:29:19.8811384Z 
2022-11-16T22:29:19.8812067Z During handling of the above exception, another exception occurred:
2022-11-16T22:29:19.8812419Z 
2022-11-16T22:29:19.8812809Z Traceback (most recent call last):
2022-11-16T22:29:19.8813440Z   File "_pydevd_bundle/pydevd_cython.pyx", line 1829, in _pydevd_bundle.pydevd_cython.ThreadTracer.__call__
2022-11-16T22:29:19.8814625Z   File "_pydevd_bundle/pydevd_cython.pyx", line 1152, in _pydevd_bundle.pydevd_cython.PyDBFrame.trace_dispatch
2022-11-16T22:29:19.8815637Z   File "_pydevd_bundle/pydevd_cython.pyx", line 1135, in _pydevd_bundle.pydevd_cython.PyDBFrame.trace_dispatch
2022-11-16T22:29:19.8816577Z   File "_pydevd_bundle/pydevd_cython.pyx", line 312, in _pydevd_bundle.pydevd_cython.PyDBFrame.do_wait_suspend
2022-11-16T22:29:19.8817650Z   File "/home/vsts/work/1/s/.tox/py37/lib/python3.7/site-packages/debugpy/_vendored/pydevd/pydevd.py", line 2066, in do_wait_suspend
2022-11-16T22:29:19.8818494Z     with self._threads_suspended_single_notification.notify_thread_suspended(thread_id, stop_reason):
2022-11-16T22:29:19.8819361Z   File "/opt/hostedtoolcache/Python/3.7.15/x64/lib/python3.7/contextlib.py", line 112, in __enter__
2022-11-16T22:29:19.8819885Z     return next(self.gen)
2022-11-16T22:29:19.8820884Z   File "/home/vsts/work/1/s/.tox/py37/lib/python3.7/site-packages/debugpy/_vendored/pydevd/pydevd.py", line 451, in notify_thread_suspended
2022-11-16T22:29:19.8821712Z     with AbstractSingleNotificationBehavior.notify_thread_suspended(self, thread_id, stop_reason):
2022-11-16T22:29:19.8822569Z   File "/opt/hostedtoolcache/Python/3.7.15/x64/lib/python3.7/contextlib.py", line 112, in __enter__
2022-11-16T22:29:19.8823120Z     return next(self.gen)
2022-11-16T22:29:19.8823995Z   File "/home/vsts/work/1/s/.tox/py37/lib/python3.7/site-packages/debugpy/_vendored/pydevd/pydevd.py", line 405, in notify_thread_suspended
2022-11-16T22:29:19.8824730Z     self.on_thread_suspend(thread_id, stop_reason)
2022-11-16T22:29:19.8825783Z   File "/home/vsts/work/1/s/.tox/py37/lib/python3.7/site-packages/debugpy/_vendored/pydevd/pydevd.py", line 376, in on_thread_suspend
2022-11-16T22:29:19.8826837Z     self.send_suspend_notification(thread_id, stop_reason)
2022-11-16T22:29:19.8828014Z   File "/home/vsts/work/1/s/.tox/py37/lib/python3.7/site-packages/debugpy/_vendored/pydevd/pydevd.py", line 445, in send_suspend_notification
2022-11-16T22:29:19.8828893Z     py_db.writer.add_command(py_db.cmd_factory.make_thread_suspend_single_notification(py_db, thread_id, stop_reason))
2022-11-16T22:29:19.8830177Z   File "/home/vsts/work/1/s/.tox/py37/lib/python3.7/site-packages/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py", line 353, in make_thread_suspend_single_notification
2022-11-16T22:29:19.8831041Z     info = set_additional_thread_info(thread)
2022-11-16T22:29:19.8831678Z   File "_pydevd_bundle/pydevd_cython.pyx", line 151, in _pydevd_bundle.pydevd_cython.set_additional_thread_info
2022-11-16T22:29:19.8833378Z   File "_pydevd_bundle/pydevd_cython.pyx", line 157, in _pydevd_bundle.pydevd_cython.set_additional_thread_info
2022-11-16T22:29:19.8834605Z AttributeError: 'NoneType' object has no attribute 'additional_info'
2022-11-16T22:29:19.8835853Z 0.01s - PyDB.do_wait_suspend
2022-11-16T22:29:19.8836544Z name: _thread2 (line: 25)
2022-11-16T22:29:19.8837654Z  file: /tmp/pytest-of-vsts/pytest-0/popen-gw5/test_step_multi_threads_progra2/code_to_debug.py
2022-11-16T22:29:19.8838256Z  event: line
2022-11-16T22:29:19.8838612Z  arg: None
2022-11-16T22:29:19.8839508Z  step: CMD_STEP_INTO (original step: <Unknown: -1>)
2022-11-16T22:29:19.8840213Z  thread: <Thread(thread2, started 139635124590336)>, thread id: pid_47755_id_139635516896528, id(thread): 139635516896528
2022-11-16T22:29:19.8841193Z 0.00s -   Stack: /tmp/pytest-of-vsts/pytest-0/popen-gw5/test_step_multi_threads_progra2/code_to_debug.py, _thread2, 25
2022-11-16T22:29:19.8842360Z 0.00s -   Stack: /opt/hostedtoolcache/Python/3.7.15/x64/lib/python3.7/threading.py, run, 870
2022-11-16T22:29:19.8843490Z 0.00s -   Stack: /opt/hostedtoolcache/Python/3.7.15/x64/lib/python3.7/threading.py, _bootstrap_inner, 926
2022-11-16T22:29:19.8844509Z 0.00s -   Stack: /opt/hostedtoolcache/Python/3.7.15/x64/lib/python3.7/threading.py, _bootstrap, 890
2022-11-16T22:29:19.8845779Z 0.00s -   Stack: /home/vsts/work/1/s/.tox/py37/lib/python3.7/site-packages/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py, __call__, 1070
2022-11-16T22:29:19.8847010Z 0.00s - Suspend not sent because stop reason is thread suspend and pause was not requested.
```

Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=microsoft_debugpy&pullRequest=1141)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=1141&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=1141&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=1141&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=1141&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=1141&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=1141&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=microsoft_debugpy&pullRequest=1141&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=microsoft_debugpy&pullRequest=1141&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=microsoft_debugpy&pullRequest=1141&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=1141&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=1141&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=1141&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=1141&metric=coverage&view=list) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=1141&metric=duplicated_lines_density&view=list) No Duplication information



Patch:
diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py
index 3c08fdca..0b21c59b 100644
--- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py
+++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py
@@ -209,12 +209,13 @@ class PyDevdAPI(object):
         else:
             py_db.post_internal_command(internal_get_thread_stack, '*')
 
-    def request_exception_info_json(self, py_db, request, thread_id, max_frames):
+    def request_exception_info_json(self, py_db, request, thread_id, thread, max_frames):
         py_db.post_method_as_internal_command(
             thread_id,
             internal_get_exception_details_json,
             request,
             thread_id,
+            thread,
             max_frames,
             set_additional_thread_info=set_additional_thread_info,
             iter_visible_frames_info=py_db.cmd_factory._iter_visible_frames_info,
diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py
index e1e93b7e..b9ac9053 100644
--- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py
+++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py
@@ -669,7 +669,7 @@ class InternalGetThreadStack(InternalThreadCommand):
 
 def internal_step_in_thread(py_db, thread_id, cmd_id, set_additional_thread_info):
     thread_to_step = pydevd_find_thread_by_id(thread_id)
-    if thread_to_step:
+    if thread_to_step is not None:
         info = set_additional_thread_info(thread_to_step)
         info.pydev_original_step_cmd = cmd_id
         info.pydev_step_cmd = cmd_id
@@ -682,7 +682,7 @@ def internal_step_in_thread(py_db, thread_id, cmd_id, set_additional_thread_info
 
 def internal_smart_step_into(py_db, thread_id, offset, child_offset, set_additional_thread_info):
     thread_to_step = pydevd_find_thread_by_id(thread_id)
-    if thread_to_step:
+    if thread_to_step is not None:
         info = set_additional_thread_info(thread_to_step)
         info.pydev_original_step_cmd = CMD_SMART_STEP_INTO
         info.pydev_step_cmd = CMD_SMART_STEP_INTO
@@ -714,7 +714,7 @@ class InternalSetNextStatementThread(InternalThreadCommand):
 
     def do_it(self, dbg):
         t = pydevd_find_thread_by_id(self.thread_id)
-        if t:
+        if t is not None:
             info = t.additional_info
             info.pydev_original_step_cmd = self.cmd_id
             info.pydev_step_cmd = self.cmd_id
@@ -1401,11 +1401,10 @@ def internal_get_description(dbg, seq, thread_id, frame_id, expression):
         dbg.writer.add_command(cmd)
 
 
-def build_exception_info_response(dbg, thread_id, request_seq, set_additional_thread_info, iter_visible_frames_info, max_frames):
+def build_exception_info_response(dbg, thread_id, thread, request_seq, set_additional_thread_info, iter_visible_frames_info, max_frames):
     '''
     :return ExceptionInfoResponse
     '''
-    thread = pydevd_find_thread_by_id(thread_id)
     additional_info = set_additional_thread_info(thread)
     topmost_frame = additional_info.get_topmost_frame(thread)
 
@@ -1540,11 +1539,11 @@ def build_exception_info_response(dbg, thread_id, request_seq, set_additional_th
     return response
 
 
-def internal_get_exception_details_json(dbg, request, thread_id, max_frames, set_additional_thread_info=None, iter_visible_frames_info=None):
+def internal_get_exception_details_json(dbg, request, thread_id, thread, max_frames, set_additional_thread_info=None, iter_visible_frames_info=None):
     ''' Fetch exception details
     '''
     try:
-        response = build_exception_info_response(dbg, thread_id, request.seq, set_additional_thread_info, iter_visible_frames_info, max_frames)
+        response = build_exception_info_response(dbg, thread_id, thread, request.seq, set_additional_thread_info, iter_visible_frames_info, max_frames)
     except:
         exc = get_exception_traceback_str()
         response = pydevd_base_schema.build_response(request, kwargs={
diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py
index 6f1dfc34..f7e8e31a 100644
--- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py
+++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_json.py
@@ -28,7 +28,6 @@ from _pydevd_bundle.pydevd_comm import build_exception_info_response
 from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
 from _pydevd_bundle import pydevd_frame_utils, pydevd_constants, pydevd_utils
 import linecache
-from _pydevd_bundle.pydevd_thread_lifecycle import pydevd_find_thread_by_id
 from io import StringIO
 from _pydev_bundle import pydev_log
 
@@ -346,10 +345,9 @@ class NetCommandFactoryJson(NetCommandFactory):
     ])
 
     @overrides(NetCommandFactory.make_thread_suspend_single_notification)
-    def make_thread_suspend_single_notification(self, py_db, thread_id, stop_reason):
+    def make_thread_suspend_single_notification(self, py_db, thread_id, thread, stop_reason):
         exc_desc = None
         exc_name = None
-        thread = pydevd_find_thread_by_id(thread_id)
         info = set_additional_thread_info(thread)
 
         preserve_focus_hint = False
@@ -375,7 +373,7 @@ class NetCommandFactoryJson(NetCommandFactory):
 
         if stop_reason == 'exception':
             exception_info_response = build_exception_info_response(
-                py_db, thread_id, -1, set_additional_thread_info, self._iter_visible_frames_info, max_frames=-1)
+                py_db, thread_id, thread, -1, set_additional_thread_info, self._iter_visible_frames_info, max_frames=-1)
             exception_info_response
 
             exc_name = exception_info_response.body.exceptionId
diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py
index bc4826b1..7df81059 100644
--- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py
+++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_net_command_factory_xml.py
@@ -293,7 +293,7 @@ class NetCommandFactory(object):
         except:
             return self.make_error_message(0, get_exception_traceback_str())
 
-    def make_thread_suspend_single_notification(self, py_db, thread_id, stop_reason):
+    def make_thread_suspend_single_notification(self, py_db, thread_id, thread, stop_reason):
         try:
             return NetCommand(CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION, 0, json.dumps(
                 {'thread_id': thread_id, 'stop_reason':stop_reason}))
diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py
index e3afdcd8..04604841 100644
--- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py
+++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command.py
@@ -743,10 +743,11 @@ class _PyDevCommandProcessor(object):
         thread_id = text
         t = pydevd_find_thread_by_id(thread_id)
         frame = None
-        if t and not getattr(t, 'pydev_do_not_trace', None):
+        if t is not None and not getattr(t, 'pydev_do_not_trace', None):
             additional_info = set_additional_thread_info(t)
             frame = additional_info.get_topmost_frame(t)
         try:
+            # Note: provide the return even if the thread is empty.
             return py_db.cmd_factory.make_get_exception_details_message(py_db, seq, thread_id, frame)
         finally:
             frame = None
diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py
index 92d671b8..42eb5991 100644
--- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py
+++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py
@@ -575,6 +575,16 @@ class PyDevJsonCommandProcessor(object):
         target_id = arguments.targetId
         if target_id is not None:
             thread = pydevd_find_thread_by_id(thread_id)
+            if thread is None:
+                response = Response(
+                    request_seq=request.seq,
+                    success=False,
+                    command=request.command,
+                    message='Unable to find thread from thread_id: %s' % (thread_id,),
+                    body={},
+                )
+                return NetCommand(CMD_RETURN, 0, response, is_json=True)
+
             info = set_additional_thread_info(thread)
             target_id_to_smart_step_into_variant = info.target_id_to_smart_step_into_variant
             if not target_id_to_smart_step_into_variant:
@@ -978,7 +988,18 @@ class PyDevJsonCommandProcessor(object):
         exception_into_arguments = request.arguments
         thread_id = exception_into_arguments.threadId
         max_frames = self._options.max_exception_stack_frames
-        self.api.request_exception_info_json(py_db, request, thread_id, max_frames)
+        thread = pydevd_find_thread_by_id(thread_id)
+        if thread is not None:
+            self.api.request_exception_info_json(py_db, request, thread_id, thread, max_frames)
+        else:
+            response = Response(
+                request_seq=request.seq,
+                success=False,
+                command=request.command,
+                message='Unable to find thread from thread_id: %s' % (thread_id,),
+                body={},
+            )
+            return NetCommand(CMD_RETURN, 0, response, is_json=True)
 
     def on_scopes_request(self, py_db, request):
         '''
diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py
index 0744abf2..2634714a 100644
--- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py
+++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py
@@ -325,7 +325,7 @@ def _run_with_unblock_threads(original_func, py_db, curr_thread, frame, expressi
             on_timeout_unblock_threads.called = True
             pydev_log.info('Resuming threads after evaluate timeout.')
             resume_threads('*', except_thread=curr_thread)
-            py_db.threads_suspended_single_notification.on_thread_resume(tid)
+            py_db.threads_suspended_single_notification.on_thread_resume(tid, curr_thread)
 
         on_timeout_unblock_threads.called = False
 
@@ -341,7 +341,7 @@ def _run_with_unblock_threads(original_func, py_db, curr_thread, frame, expressi
             mark_thread_suspended(curr_thread, CMD_SET_BREAK)
             py_db.threads_suspended_single_notification.increment_suspend_time()
             suspend_all_threads(py_db, except_thread=curr_thread)
-            py_db.threads_suspended_single_notification.on_thread_suspend(tid, CMD_SET_BREAK)
+            py_db.threads_suspended_single_notification.on_thread_suspend(tid, curr_thread, CMD_SET_BREAK)
 
 
 def _evaluate_with_timeouts(original_func):
diff --git a/src/debugpy/_vendored/pydevd/pydevd.py b/src/debugpy/_vendored/pydevd/pydevd.py
index d83ac9dc..702d6bec 100644
--- a/src/debugpy/_vendored/pydevd/pydevd.py
+++ b/src/debugpy/_vendored/pydevd/pydevd.py
@@ -298,7 +298,7 @@ class AbstractSingleNotificationBehavior(object):
 
     # On do_wait_suspend, use notify_thread_suspended:
     def do_wait_suspend(...):
-        with single_notification_behavior.notify_thread_suspended(thread_id):
+        with single_notification_behavior.notify_thread_suspended(thread_id, thread, reason):
             ...
     '''
 
@@ -308,7 +308,7 @@ class AbstractSingleNotificationBehavior(object):
         '_lock',
         '_next_request_time',
         '_suspend_time_request',
-        '_suspended_thread_ids',
+        '_suspended_thread_id_to_thread',
         '_pause_requested',
         '_py_db',
     ]
@@ -322,10 +322,10 @@ class AbstractSingleNotificationBehavior(object):
         self._last_resume_notification_time = -1
         self._suspend_time_request = self._next_request_time()
         self._lock = thread.allocate_lock()
-        self._suspended_thread_ids = set()
+        self._suspended_thread_id_to_thread = {}
         self._pause_requested = False
 
-    def send_suspend_notification(self, thread_id, stop_reason):
+    def send_suspend_notification(self, thread_id, thread, stop_reason):
         raise AssertionError('abstract: subclasses must override.')
 
     def send_resume_notification(self, thread_id):
@@ -351,21 +351,22 @@ class AbstractSingleNotificationBehavior(object):
 
     def _notify_after_timeout(self, global_suspend_time):
         with self._lock:
-            if self._suspended_thread_ids:
+            if self._suspended_thread_id_to_thread:
                 if global_suspend_time > self._last_suspend_notification_time:
                     self._last_suspend_notification_time = global_suspend_time
                     # Notify about any thread which is currently suspended.
                     pydev_log.info('Sending suspend notification after timeout.')
-                    self.send_suspend_notification(next(iter(self._suspended_thread_ids)), CMD_THREAD_SUSPEND)
+                    thread_id, thread = next(iter(self._suspended_thread_id_to_thread.items()))
+                    self.send_suspend_notification(thread_id, thread, CMD_THREAD_SUSPEND)
 
-    def on_thread_suspend(self, thread_id, stop_reason):
+    def on_thread_suspend(self, thread_id, thread, stop_reason):
         with self._lock:
             pause_requested = self._pause_requested
             if pause_requested:
                 # When a suspend notification is sent, reset the pause flag.
                 self._pause_requested = False
 
-            self._suspended_thread_ids.add(thread_id)
+            self._suspended_thread_id_to_thread[thread_id] = thread
 
             # CMD_THREAD_SUSPEND should always be a side-effect of a break, so, only
             # issue for a CMD_THREAD_SUSPEND if a pause is pending.
@@ -373,7 +374,7 @@ class AbstractSingleNotificationBehavior(object):
                 if self._suspend_time_request > self._last_suspend_notification_time:
                     pydev_log.info('Sending suspend notification.')
                     self._last_suspend_notification_time = self._suspend_time_request
-                    self.send_suspend_notification(thread_id, stop_reason)
+                    self.send_suspend_notification(thread_id, thread, stop_reason)
                 else:
                     pydev_log.info(
                         'Suspend not sent (it was already sent). Last suspend % <= Last resume %s',
@@ -385,10 +386,10 @@ class AbstractSingleNotificationBehavior(object):
                     'Suspend not sent because stop reason is thread suspend and pause was not requested.',
                 )
 
-    def on_thread_resume(self, thread_id):
+    def on_thread_resume(self, thread_id, thread):
         # on resume (step, continue all):
         with self._lock:
-            self._suspended_thread_ids.remove(thread_id)
+            self._suspended_thread_id_to_thread.pop(thread_id)
             if self._last_resume_notification_time < self._last_suspend_notification_time:
                 pydev_log.info('Sending resume notification.')
                 self._last_resume_notification_time = self._last_suspend_notification_time
@@ -401,12 +402,12 @@ class AbstractSingleNotificationBehavior(object):
                 )
 
     @contextmanager
-    def notify_thread_suspended(self, thread_id, stop_reason):
-        self.on_thread_suspend(thread_id, stop_reason)
+    def notify_thread_suspended(self, thread_id, thread, stop_reason):
+        self.on_thread_suspend(thread_id, thread, stop_reason)
         try:
             yield  # At this point the thread must be actually suspended.
         finally:
-            self.on_thread_resume(thread_id)
+            self.on_thread_resume(thread_id, thread)
 
 
 class ThreadsSuspendedSingleNotification(AbstractSingleNotificationBehavior):
@@ -439,16 +440,18 @@ class ThreadsSuspendedSingleNotification(AbstractSingleNotificationBehavior):
                 callback()
 
     @overrides(AbstractSingleNotificationBehavior.send_suspend_notification)
-    def send_suspend_notification(self, thread_id, stop_reason):
+    def send_suspend_notification(self, thread_id, thread, stop_reason):
         py_db = self._py_db()
         if py_db is not None:
-            py_db.writer.add_command(py_db.cmd_factory.make_thread_suspend_single_notification(py_db, thread_id, stop_reason))
+            py_db.writer.add_command(
+                py_db.cmd_factory.make_thread_suspend_single_notification(
+                    py_db, thread_id, thread, stop_reason))
 
     @overrides(AbstractSingleNotificationBehavior.notify_thread_suspended)
     @contextmanager
-    def notify_thread_suspended(self, thread_id, stop_reason):
+    def notify_thread_suspended(self, thread_id, thread, stop_reason):
         if self.multi_threads_single_notification:
-            with AbstractSingleNotificationBehavior.notify_thread_suspended(self, thread_id, stop_reason):
+            with AbstractSingleNotificationBehavior.notify_thread_suspended(self, thread_id, thread, stop_reason):
                 yield
         else:
             yield
@@ -2063,7 +2066,7 @@ class PyDB(object):
 
                     from_this_thread.append(frame_custom_thread_id)
 
-            with self._threads_suspended_single_notification.notify_thread_suspended(thread_id, stop_reason):
+            with self._threads_suspended_single_notification.notify_thread_suspended(thread_id, thread, stop_reason):
                 keep_suspended = self._do_wait_suspend(thread, frame, event, arg, suspend_type, from_this_thread, frames_tracker)
 
         frames_list = None


Test patch:
diff --git a/src/debugpy/_vendored/pydevd/tests_python/tests_single_notification.py b/src/debugpy/_vendored/pydevd/tests_python/test_single_notification.py
similarity index 97%
rename from src/debugpy/_vendored/pydevd/tests_python/tests_single_notification.py
rename to src/debugpy/_vendored/pydevd/tests_python/test_single_notification.py
index fbbfbf98..02c48cbc 100644
--- a/src/debugpy/_vendored/pydevd/tests_python/tests_single_notification.py
+++ b/src/debugpy/_vendored/pydevd/tests_python/test_single_notification.py
@@ -8,6 +8,7 @@ import pytest
 from _pydevd_bundle.pydevd_daemon_thread import run_as_pydevd_daemon_thread
 from tests_python.debugger_unittest import CMD_THREAD_SUSPEND, CMD_STEP_OVER, CMD_SET_BREAK
 from _pydev_bundle.pydev_override import overrides
+import threading
 
 STATE_RUN = 1
 STATE_SUSPEND = 2
@@ -19,6 +20,7 @@ class _ThreadInfo(object):
 
     def __init__(self):
         self.state = STATE_RUN
+        self.thread = threading.Thread()
         self.thread_id = self.next_thread_id()
 
 
@@ -47,7 +49,7 @@ class _CustomSingleNotificationBehavior(AbstractSingleNotificationBehavior):
         self.notification_queue.put('suspend')
 
     def do_wait_suspend(self, thread_info, stop_reason):
-        with self.notify_thread_suspended(thread_info.thread_id, stop_reason=stop_reason):
+        with self.notify_thread_suspended(thread_info.thread_id, thread_info.thread, stop_reason=stop_reason):
             while thread_info.state == STATE_SUSPEND:
                 time.sleep(.1)
 
@@ -93,7 +95,9 @@ def join_thread(t):
 class _DummyPyDB(object):
 
     def __init__(self):
+        from _pydevd_bundle.pydevd_timeout import TimeoutTracker
         self.created_pydb_daemon_threads = {}
+        self.timeout_tracker = TimeoutTracker(self)
 
 
 def test_single_notification_1(single_notification_behavior, notification_queue):
@@ -230,7 +234,7 @@ def test_single_notification_3(single_notification_behavior, notification_queue,
 
     join_thread(t2)
     assert notification_queue.qsize() == 0
-    assert not single_notification_behavior._suspended_thread_ids
+    assert not single_notification_behavior._suspended_thread_id_to_thread
 
     # Now, no threads are running and pause is pressed
     # (maybe we could do a thread dump in this case as this