microsoft/ptvsd

Debugger doesn't stop on uncaught exceptions in third party libraries

andersea opened this issue · 11 comments

Environment data

  • VS Code version: 1.41.1
  • Extension version (available under the Extensions sidebar): 2020.1.57204
  • OS and version: Windows 10 Version 1909
  • Python version (& distribution if applicable, e.g. Anaconda): 3.7.3 AMD 64
  • Type of virtual environment used (N/A | venv | virtualenv | conda | ...): venv with poetry
  • Relevant/affected Python packages and their versions: N/A
  • Jedi or Language Server? (i.e. what is "python.jediEnabled" set to; more info microsoft/vscode-python#3977): false (language server)

Additional settings:

In the debugger tab:
I have 'Raised Exceptions' unchecked.
I have 'Uncaught Exceptions' checked.
(As a side note, I think it is very unclear how these settings are supposed to interact. I can't see anything in any documentation anywhere.)

launch.json:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Current File",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal"
        }
    ]
}

Expected behaviour

If an exception is raised in a third party library. Code should stop on the line of code that called into the third party library.

Actual behaviour

The exception is ignored by the debugger, causing the program to exit with an error and the exception to be printed to the terminal.

Steps to reproduce:

(0. I tested both sync and async versions. I use trio as async library, so this needs to be added to the venv.)

  1. I created a simple library with the following test functions. I used poetry with the command 'new' to create a new project, and in the project __init__.py file I added the following code.

__init__.py

__version__ = '0.1.2'

import trio
import time

async def aiter_thrower():
    i = 0
    while i < 5:
        yield i
        await trio.sleep(0.5)
        i += 1
    raise RuntimeError('Auch!')

async def afunc_thrower():
    await trio.sleep(0.5)
    raise RuntimeError('Auch!')

def iter_thrower():
    i = 0
    while i < 5:
        yield i
        time.sleep(0.5)
        i += 1
    raise RuntimeError('Auch!')

def func_thrower():
    time.sleep(0.5)
    raise RuntimeError('Auch!')
  1. I build the library using poetry build. This creates a wheel file in the dist subdir.

  2. I create another test project using poetry new.

  3. In the test project I add the wheel file using poetry add ..\test_library\dist\<name of wheel file>

  4. In the test project I add the following test code:

import trio
from async_test_lib import aiter_thrower, afunc_thrower, iter_thrower, func_thrower

async def test_afunc():
    await afunc_thrower()

async def test_aiter():
    async for x in aiter_thrower():
        print(f'Got {x}')

if __name__ == '__main__':
    print('Uncomment one of these sections in turn.')
    # Test aiter_thrower
#    trio.run(test_aiter)
    # Test afunc_thrower
#    trio.run(test_afunc)
    # Test func_thrower
#    func_thrower()
    # Test iter_thrower
#    for x in iter_thrower():
#        print(f'Got {x}')
  1. In turn, uncomment one of the sections to test each type of raised exception.

Logs

Output for Python in the Output panel (ViewOutput, change the drop-down the upper-right of the Output panel to Python)

(stream-trio-test-gbo-I5e8-py3.7) PS C:\Users\Anders\Documents\python\stream-trio-test> cd 'c:\Users\Anders\Documents\python\stream-trio-test'; ${env:PYTHONIOENCODING}='UTF-8'; ${env:PYTHONUNBUFFERED}='1'; & 'C:\Users\Anders\AppData\Local\pypoetry\Cache\virtualenvs\stream-trio-test-gbo-I5e8-py3.7\Scripts\python.exe' 'c:\Users\Anders\.vscode\extensions\ms-python.python-2020.1.57204\pythonFiles\ptvsd_launcher.py' '--default' '--client' 
'--host' 'localhost' '--port' '63052' 'c:\Users\Anders\Documents\python\stream-trio-test\test_asyinciter_except.py' 
Start
Got 0
Got 1
Got 2
Got 3
Got 4
Traceback (most recent call last):
  File "c:\Users\Anders\.vscode\extensions\ms-python.python-2020.1.57204\pythonFiles\ptvsd_launcher.py", line 43, in <module>
    main(ptvsdArgs)
  File "c:\Users\Anders\.vscode\extensions\ms-python.python-2020.1.57204\pythonFiles\lib\python\old_ptvsd\ptvsd\__main__.py", line 432, in main   
    run()
  File "c:\Users\Anders\.vscode\extensions\ms-python.python-2020.1.57204\pythonFiles\lib\python\old_ptvsd\ptvsd\__main__.py", line 316, in run_file
    runpy.run_path(target, run_name='__main__')
  File "C:\Users\Anders\AppData\Local\Programs\Python\Python37\lib\runpy.py", line 263, in run_path
RuntimeError: Auch!

Output from Console under the Developer Tools panel (toggle Developer Tools on under Help; turn on source maps to make any tracebacks be useful by running Enable source map support for extension debugging)


Try adding "justMyCode":false in your launch json. The debugger by default ignores anything that is outside of the current working directory.

This scenario should not be affected by that setting - it should report the exception the moment it crosses into user code.

It looks like async is what's causing the problem here.

The same thing happens with purely sync code.

Bad library:

__version__ = '0.1.0'

import time

def bad_func():
    print('Gone bad')
    time.sleep(0.5)
    raise RuntimeError('Bad!')

def bad_gen():
    print('Bad gen')
    yield 1
    time.sleep(0.5)
    yield 2
    time.sleep(0.5)
    print('This is going to hurt!')
    raise RuntimeError('That hurt!')

Build the library and install the wheel into another project, like I described in the first post.

Run a simple test function:

from bad_sync_lib import bad_func, bad_gen

print('Testing bad func')
bad_func()
print('We should never get to this place')

The debugger ignores the exception and the error is output into the terminal.

@karthiknadig thanks for the suggestion, but that is not what I am looking for. Using "justMyCode":false is designed to make the debugger halt inside the third party module code. That is not what I want. Like @int19h comments, it should stop as soon as it crosses into user code.

Essentially, what I expect can be illustrated with this example:

Screenshot of very simple code that does what I expect

You wouldn't expect it to suddenly jump into standard library code in this case? It should stop at the open() function call line. I would expect the same behaviour with third party modules.

@andersea Ah!. We addressed a bunch of async issues in the new version of the debugger. Can you try using the insiders version of the python extension (from here)? Add this to your user settings.json to enable the new debugger and reload:

    "python.experiments.optInto": [
        "DebugAdapterFactory - experiment",
        "PtvsdWheels37 - experiment"
    ]

The above steps should enable the new debugger. If everything works well, then when you start debugging in the command line it should have new_ptvsd in the path.

Sorry, still doesn't work. As I mentioned, I don't think this is async related actually.

Console output:

(test-bad-sync-lib-dHqxARaN-py3.7) PS C:\Users\Anders\Documents\python\test_bad_sync_lib> ${env:PTVSD_LAUNCHER_PORT}='50514'; & 'C:\Users\Anders\AppData\Local\pypoetry\Cache\virtualenvs\test-bad-sync-lib-dHqxARaN-py3.7\Scripts\python.exe' 'c:\Users\Anders\.vscode\extensions\ms-python.python-2020.1.57377-dev\pythonFiles\lib\python\new_ptvsd\wheels\ptvsd\launcher' 'c:\Users\Anders\Documents\python\test_bad_sync_lib\test.py'
Try to open file that doesnt exist.
Gone bad
Traceback (most recent call last):
  File "C:\Users\Anders\AppData\Local\Programs\Python\Python37\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "C:\Users\Anders\AppData\Local\Programs\Python\Python37\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "c:\Users\Anders\.vscode\extensions\ms-python.python-2020.1.57377-dev\pythonFiles\lib\python\new_ptvsd\wheels\ptvsd\__main__.py", line 45, 
in <module>
    cli.main()
  File "c:\Users\Anders\.vscode\extensions\ms-python.python-2020.1.57377-dev\pythonFiles\lib\python\new_ptvsd\wheels\ptvsd/..\ptvsd\server\cli.py", line 361, in main
    run()
  File "c:\Users\Anders\.vscode\extensions\ms-python.python-2020.1.57377-dev\pythonFiles\lib\python\new_ptvsd\wheels\ptvsd/..\ptvsd\server\cli.py", line 203, in run_file
    runpy.run_path(options.target, run_name="__main__")
  File "C:\Users\Anders\AppData\Local\Programs\Python\Python37\lib\runpy.py", line 263, in run_path
    pkg_name=pkg_name, script_name=fname)
  File "C:\Users\Anders\AppData\Local\Programs\Python\Python37\lib\runpy.py", line 96, in _run_module_code
    mod_name, mod_spec, pkg_name, script_name)
  File "C:\Users\Anders\AppData\Local\Programs\Python\Python37\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "c:\Users\Anders\Documents\python\test_bad_sync_lib\test.py", line 5, in <module>
    bad_func()
  File "C:\Users\Anders\AppData\Local\pypoetry\Cache\virtualenvs\test-bad-sync-lib-dHqxARaN-py3.7\lib\site-packages\bad_sync_lib\__init__.py", line 8, in bad_func
    raise RuntimeError('Bad!')
RuntimeError: Bad!

This is using my second example library with only sync code, from my previous comment.

If I enable the option Raised exceptions -

option raised exceptions screenshot

then the example behaves pretty much as I expect, however this will come with the drawback of halting on any exception, no matter if I handle it or not. A lot of code in python uses exceptions as part of normal branching:

https://devblogs.microsoft.com/python/idiomatic-python-eafp-versus-lbyl/

So I open myself up to being flooded with exceptions that are expected and already handled, like in this screenshot:

Screenshot of debugger catching a handled exception

The uncaught exceptions option exists for exactly this purpose.

(Note that, although in the screenshot I write in the comment that I expect the debugger to stop, in the actual screenshot'ed code, it should not stop, since in this case I catch the error. It only stops because I am using the Raised exceptions option.)

@fabioz Can you look at this one?

Sure... I was actually taking a look at uncaught exceptions in third party libraries as a part #1946 (see my comment: #1946 (comment))... I'll double check to see if this use case is also fixed when I finish #1946.

@fabioz @karthiknadig is this fix included in current vscode-python? Because it still doesn't work.

@andersea It should be included in ptvsd 5.0.0a12. You can check the version that you have with:

import ptvsd
print(ptvsd.__version__)

@int19h Thanks, I had version 4, so I deleted that, made sure the above optin section was in my settings.json and now it says 5.0.0a12. The test case appears to work now. Thanks.

Edit: I am using the insiders build of the extension, also.