diff --git a/examples/debugging/root_cancelled_but_child_is_in_tty_lock.py b/examples/debugging/root_cancelled_but_child_is_in_tty_lock.py new file mode 100644 index 0000000..04ec767 --- /dev/null +++ b/examples/debugging/root_cancelled_but_child_is_in_tty_lock.py @@ -0,0 +1,48 @@ +import tractor + + +async def name_error(): + "Raise a ``NameError``" + getattr(doggypants) + + +async def spawn_until(depth=0): + """"A nested nursery that triggers another ``NameError``. + """ + async with tractor.open_nursery() as n: + if depth < 1: + # await n.run_in_actor('breakpoint_forever', breakpoint_forever) + await n.run_in_actor('name_error', name_error) + else: + depth -= 1 + await n.run_in_actor(f'spawn_until_{depth}', spawn_until, depth=depth) + + +async def main(): + """The main ``tractor`` routine. + + The process tree should look as approximately as follows when the debugger + first engages: + + python examples/debugging/multi_nested_subactors_bp_forever.py + ├─ python -m tractor._child --uid ('spawner1', '7eab8462 ...) + │ └─ python -m tractor._child --uid ('spawn_until_0', '3720602b ...) + │ └─ python -m tractor._child --uid ('name_error', '505bf71d ...) + │ + └─ python -m tractor._child --uid ('spawner0', '1d42012b ...) + └─ python -m tractor._child --uid ('name_error', '6c2733b8 ...) + + """ + async with tractor.open_nursery() as n: + + # spawn both actors + portal = await n.run_in_actor('spawner0', spawn_until, depth=0) + portal1 = await n.run_in_actor('spawner1', spawn_until, depth=1) + + # nursery cancellation should be triggered due to propagated error + await portal.result() + await portal1.result() + + +if __name__ == '__main__': + tractor.run(main, debug_mode=True, loglevel='warning') diff --git a/tests/test_debugger.py b/tests/test_debugger.py index 2540c2e..76113c9 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -331,3 +331,16 @@ def test_multi_nested_subactors_error_through_nurseries(spawn): before = str(child.before.decode()) assert "NameError" in before + + +def test_root_nursery_cancels_before_child_releases_tty_lock(spawn): + """Exemplifies a bug where the root sends a cancel message before a nested child + which has the tty lock (and is in pdb) doesn't cancel after exiting the debugger. + """ + child = spawn('root_cancelled_but_child_is_in_tty_lock') + + for _ in range(5): + child.expect(r"\(Pdb\+\+\)") + child.sendline('c') + + # child.expect(pexpect.EOF)