tractor icon indicating copy to clipboard operation
tractor copied to clipboard

`IPython` integration 😎

Open goodboy opened this issue 4 years ago • 3 comments

Like it sounds, with more refinements to come!

This is a POC and seems to do about what I'd like; would appreciate lurker feedback 😉

ping @mikenerone, original author of the base gists for this:

  • https://gist.github.com/mikenerone/786ce75cf8d906ae4ad1e0b57933c23f
  • https://gist.github.com/mikenerone/3640fdd450b4ca55ee8df4d4da5a7165

TODO:

  • [ ] link to @mikenerone's github in credits
  • [ ] adjust credit to point to @mikenerone's GH
  • [ ] get colors and user's config workin
    • see config docs: https://ipython.readthedocs.io/en/stable/config/details.html#editor-configuration
  • [ ] toy with a default set of actor mgmt / respawn APIs?

goodboy avatar Mar 20 '22 22:03 goodboy

Turns out there might be a better solution for what we actually want (await <blah> inside any REPL, but mostly our debugger UX).


from @smurfix on gitter:

import trio
import greenback as gb
AW=gb.await_

async def wat():
    await trio.sleep(0.1)
    return "yes"

async def run():
    await gb.ensure_portal()
    breakpoint()
    pass

trio.run(run)

which can be called from repl like:

(Pdb) AW(wat())
'yes'

with follow up from @oremanj actually using pdb++ 💥 :

$ python3.8 -c "import trio, pdb, greenback; trio.run(greenback.with_portal_run_sync, lambda: breakpoint())"
(Pdb++) greenback.await_(trio.sleep(1))
<1sec delay>

also a further extended example 🏄🏼

$ python3.8 -c "import trio, greenback, code; trio.run(greenback.with_portal_run_sync, code.interact)"
>>> import trio
>>> from greenback import await_ as aw, async_context as acm
>>> async def task(interval, msg):
...   while True:
...     await trio.sleep(interval)
...     print(msg)
...
>>> with acm(trio.open_nursery()) as nursery:
...   nursery.start_soon(task, 2, "every two seconds")
...   nursery.start_soon(task, 0.75, "every 3/4 second")
...   aw(task(1.7, "every 1.7 seconds"))
...
every 3/4 second
every 3/4 second
every 1.7 seconds
every two seconds
every 3/4 second
every 3/4 second
every 1.7 seconds
every 3/4 second
every two seconds
^CTraceback (most recent call last):
  File "/usr/lib/python3.8/code.py", line 90, in runcode
    exec(code, self.locals)
  File "<console>", line 4, in <module>
  File "/usr/lib/python3.8/site-packages/greenback/_impl.py", line 653, in await_
    raise exception_from_greenbacked_function
  File "<console>", line 3, in task
  File "/usr/lib/python3.8/site-packages/trio/_timeouts.py", line 75, in sleep
    await sleep_until(trio.current_time() + seconds)
  File "/usr/lib/python3.8/site-packages/trio/_timeouts.py", line 56, in sleep_until
    await sleep_forever()
  File "/usr/lib/python3.8/site-packages/trio/_timeouts.py", line 40, in sleep_forever
    await trio.lowlevel.wait_task_rescheduled(lambda _: trio.lowlevel.Abort.SUCCEEDED)
  File "/usr/lib/python3.8/site-packages/trio/_core/_traps.py", line 166, in wait_task_rescheduled
    return (await _async_yield(WaitTaskRescheduled(abort_func))).unwrap()
  File "/usr/lib/python3.8/site-packages/outcome/_sync.py", line 111, in unwrap
    raise captured_error
  File "/usr/lib/python3.8/site-packages/trio/_core/_run.py", line 1178, in raise_cancel
    raise KeyboardInterrupt
KeyboardInterrupt
>>>

goodboy avatar Jan 14 '23 21:01 goodboy

To make this work anywhere we should teach Trio's task creation code to unconditionally create a Greenback portal in every new task; otherwise debugging at random breakpoints won't work.

smurfix avatar Jan 15 '23 10:01 smurfix

@smurfix agreed, this is already an issue with debugging single task crashes within a nursery as well.

Currently on a task crash, you can't breakpoint() at the (specific task's) error stack frame and instead always end up at the nursery exit; so, an extended nursery is needed for both of these cases:

  • handling task-level crashes with a breakpoint() and engaging the REPL in that task's frame,
  • overriding the global hook to always allow using await in the REPL (and obviously making the particular REPL plug-gable in the longer run)

Further, this probably plays best with the idea of a OCONursery (one-cancels-one) style nursery where every task has an associated individual cancel scope that can be managed independently of other tasks such that respawns can happen on a per-task-failure scenario.

goodboy avatar Jan 16 '23 23:01 goodboy