gotcha / ipdb

Integration of IPython pdb
BSD 3-Clause "New" or "Revised" License
1.84k stars 147 forks source link

Trouble with asyncio and ipdb #184

Open om-saran opened 4 years ago

om-saran commented 4 years ago

I'm finding trouble using ipdb in this scenario. Not sure if I'm missing something here

import asyncio
import ipdb

async def main():
  await asyncio.sleep(1)
  ipdb.set_trace()

asyncio.run(main())

The error thrown: $ python ipd.py

--Return--
None
> /Users/om.saran/wspace/buffer_manager/ipd.py(6)main()
      5   await asyncio.sleep(1)
----> 6   ipdb.set_trace()
      7

ipdb>
Traceback (most recent call last):
  File "ipd.py", line 8, in <module>
    asyncio.run(main())
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/base_events.py", line 599, in run_until_complete
    self.run_forever()
  File "ipd.py", line 6, in main
    ipdb.set_trace()
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/bdb.py", line 92, in trace_dispatch
    return self.dispatch_return(frame, arg)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/bdb.py", line 151, in dispatch_return
    self.user_return(frame, arg)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/pdb.py", line 293, in user_return
    self.interaction(frame, None)
  File "/Users/om.saran/wspace/buffer_manager/env/lib/python3.8/site-packages/IPython/core/debugger.py", line 291, in interaction
    OldPdb.interaction(self, frame, traceback)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/pdb.py", line 356, in interaction
    self._cmdloop()
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/pdb.py", line 321, in _cmdloop
    self.cmdloop()
  File "/Users/om.saran/wspace/buffer_manager/env/lib/python3.8/site-packages/IPython/terminal/debugger.py", line 114, in cmdloop
    line = self.pt_app.prompt()
  File "/Users/om.saran/wspace/buffer_manager/env/lib/python3.8/site-packages/prompt_toolkit/shortcuts/prompt.py", line 986, in prompt
    return self.app.run()
  File "/Users/om.saran/wspace/buffer_manager/env/lib/python3.8/site-packages/prompt_toolkit/application/application.py", line 788, in run
    return get_event_loop().run_until_complete(self.run_async(pre_run=pre_run))
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/base_events.py", line 599, in run_until_complete
    self.run_forever()
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/base_events.py", line 554, in run_forever
    raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running

OS: MacOS 10.13.6 Python version: 3.8.1 ipython version: 7.11.1 ipdb version: 0.12.3

The same works fine with pdb

gotcha commented 4 years ago

ipdb is a lightweight wrapper around IPython debugger. Have you checked IPython issue tracker ?

apatrushev commented 4 years ago

I came across this problem multiple times in different projects. In many situations project authors recommends to avoid using ipdb. Unfortunately we have not much choices around. I decided to find a solution this time and found it. It is definitely ugly, but it works for me pretty fine:

def threaded(func):
    def wrapper(*args, **kwargs):
        result = []
        def target():
            result.append(func(*args, **kwargs))
        thread = threading.Thread(target=target)
        thread.start()
        thread.join()
        return result[0]
    return wrapper
from IPython.terminal.debugger import TerminalPdb
TerminalPdb.cmdloop = threaded(TerminalPdb.cmdloop)

Probably the problem can be fixed somewhere inside prompt-toolkit. pdb will not help with it - he knows exactly nothing about asyncio.

gotcha commented 4 years ago

@apatrushev Thanks for documenting your workaround.

brianmaissy commented 4 years ago

I started to experience this issue when I upgraded my ipython from 7.9.0 to 7.12.0 (looks like the change occured in 7.10.0: https://github.com/ipython/ipython/commit/c2a5384f364ecc5dded4ad892f13527880ae8517) in which ipython started to support prompt_toolkit 3.0.

I opened an issue in prompt_toolkit (https://github.com/prompt-toolkit/python-prompt-toolkit/issues/1084) to see if they have any suggestions, but very possibly ipython (or even ipdb) needs to find a workaround, because it looks the prompt_toolkit app was not designed to be run from inside an event loop.

A temporary workaround which worked for me is to pin my prompt_toolkit to 2.0.10.

brianmaissy commented 4 years ago

Fixed in ipython: https://github.com/ipython/ipython/pull/12141

gotcha commented 4 years ago

@om-saran When new IPython is released, can you confirm and close ?

MrMino commented 3 years ago

@brianmaissy I have doubts whether this was the right way to go. Turns out that this makes IPDB hang up if prompt_toolkit was talking to a "bad file descriptior", even with sset_trace(). Use entr to see what I'm talking about:

find . -iname '*.py' | entr -c python -c "import ipdb; ipdb.sset_trace()"

Previously, this would show the following tb:

[...]
  File "/home/mrmino/.virtualenvs/tmp-f4b5e3c63682160/lib/python3.8/site-packages/prompt_toolkit/application/application.py", line 696, in _run_async
    with self.input.raw_mode(), self.input.attach(
  File "/home/mrmino/.virtualenvs/tmp-f4b5e3c63682160/lib/python3.8/site-packages/prompt_toolkit/input/vt100.py", line 257, in __enter__
    os.write(self.fileno, b"\x1b[?1l")
OSError: [Errno 9] Bad file descriptor

Now it just hangs without responding to SIGTERM, which leads me to believe that it also hangs up destructively when used e.g. by mistake in CICD test runs.

brianmaissy commented 3 years ago

@MrMino I'm not familiar with the code enough to feel confident trying to address this.

Maybe the best way to address it is to open a separate issue?

Also maybe @jonathanslenders has some insight?

MrMino commented 3 years ago

@brianmaissy I tried to fix this in different ways for the last few hours and I now believe that it comes down to a fundamental limitation with asyncio: switching / recursive event loops are not possible.

In this context having a debugger run an event loop in a separate thread seems like the most sane thing to do. We'd just need to check if the file descriptor for stdout is something usable.

pankk commented 3 months ago

I've been struggling with this one for a couple of days. I've tried a bunch of (ipdb, ipython, prompt-toolkit) version combinations and the one that's working the best for me is:

ipdb==0.10.0 ipython==4.2.1 prompt-toolkit==2.0.10

This only gives me

 DeprecationWarning: inspect.getargspec() is 
  deprecated since Python 3.0, use inspect.signature() or inspect.getfullargspec()

as opposed to one of the following Exception '0 (FD 0) is already registered' Exception: Event loop is already running your terminal doesn't support cursor position requests (CPR)

RuntimeError: Task <Task pending name='Task-6' coro=<Application.run_asy
        nc() running at /usr/local/lib/python3.9/dist-packages/prompt_toolkit/ap
        plication/application.py:856> cb=[_run_until_complete_cb() at /usr/lib/p
        ython3.9/asyncio/base_events.py:184]> got Future <Task pending name='Tas
        k-53' coro=<KeyProcessor._start_timeout.<locals>.wait() running at /usr/
        local/lib/python3.9/dist-packages/prompt_toolkit/key_binding/key_process
        or.py:397> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at
         0x7fd30c5f7e50>()]>> attached to a different loop

When I call "self" for example when debugging with ipdb, I'll get a different result on the next call, and the first one again on the third call, I guess because of the asynchronous nature of the application/framework (Odoo) I'm developing in. IIRC this did not happen with ipython versions >= 5.