From 63cdb0891f9ea57773c950c0eeba31b0b74a0939 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Sat, 15 Apr 2023 19:43:20 -0400 Subject: [PATCH 01/15] Switch to `pdbp` since noone is maintaining `pdbpp` --- setup.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index 88d6612..d26deb9 100755 --- a/setup.py +++ b/setup.py @@ -26,12 +26,12 @@ with open('docs/README.rst', encoding='utf-8') as f: setup( name="tractor", version='0.1.0a6dev0', # alpha zone - description='structured concurrrent "actors"', + description='structured concurrrent `trio`-"actors"', long_description=readme, license='AGPLv3', author='Tyler Goodlet', maintainer='Tyler Goodlet', - maintainer_email='jgbt@protonmail.com', + maintainer_email='goodboy_foss@protonmail.com', url='https://github.com/goodboy/tractor', platforms=['linux', 'windows'], packages=[ @@ -52,16 +52,14 @@ setup( # tooling 'tricycle', 'trio_typing', - - # tooling 'colorlog', 'wrapt', - # serialization + # IPC serialization 'msgspec', # debug mode REPL - 'pdbpp', + 'pdbp', # pip ref docs on these specs: # https://pip.pypa.io/en/stable/reference/requirement-specifiers/#examples @@ -73,10 +71,9 @@ setup( # https://github.com/pdbpp/fancycompleter/issues/37 'pyreadline3 ; platform_system == "Windows"', - ], tests_require=['pytest'], - python_requires=">=3.9", + python_requires=">=3.10", keywords=[ 'trio', 'async', From 3d202272c4290cd38ae06a4519c34b824b9fb48e Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Sat, 15 Apr 2023 19:43:58 -0400 Subject: [PATCH 02/15] Change over debugger tests to use `PROMPT` var.. --- tests/test_debugger.py | 71 +++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/tests/test_debugger.py b/tests/test_debugger.py index 7885034..5f90703 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -95,7 +95,7 @@ def spawn( return _spawn -PROMPT = r"\(Pdb\+\+\)" +PROMPT = r"\(Pdb\+\)" def expect( @@ -151,6 +151,7 @@ def ctlc( use_ctlc = request.param + # TODO: we can remove this bc pdbp right? if ( sys.version_info <= (3, 10) and use_ctlc @@ -231,7 +232,7 @@ def test_root_actor_bp(spawn, user_in_out): child = spawn('root_actor_breakpoint') # scan for the pdbpp prompt - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) assert 'Error' not in str(child.before) @@ -272,7 +273,7 @@ def do_ctlc( if expect_prompt: before = str(child.before.decode()) time.sleep(delay) - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) time.sleep(delay) if patt: @@ -291,7 +292,7 @@ def test_root_actor_bp_forever( # entries for _ in range(10): - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) if ctlc: do_ctlc(child) @@ -301,7 +302,7 @@ def test_root_actor_bp_forever( # do one continue which should trigger a # new task to lock the tty child.sendline('continue') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) # seems that if we hit ctrl-c too fast the # sigint guard machinery might not kick in.. @@ -312,10 +313,10 @@ def test_root_actor_bp_forever( # XXX: this previously caused a bug! child.sendline('n') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) child.sendline('n') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) # quit out of the loop child.sendline('q') @@ -339,7 +340,7 @@ def test_subactor_error( child = spawn('subactor_error') # scan for the pdbpp prompt - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) before = str(child.before.decode()) assert "Attaching to pdb in crashed actor: ('name_error'" in before @@ -359,7 +360,7 @@ def test_subactor_error( # creating actor child.sendline('continue') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) before = str(child.before.decode()) # root actor gets debugger engaged @@ -387,7 +388,7 @@ def test_subactor_breakpoint( child = spawn('subactor_breakpoint') # scan for the pdbpp prompt - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) before = str(child.before.decode()) assert "Attaching pdb to actor: ('breakpoint_forever'" in before @@ -396,7 +397,7 @@ def test_subactor_breakpoint( # entries for _ in range(10): child.sendline('next') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) if ctlc: do_ctlc(child) @@ -404,7 +405,7 @@ def test_subactor_breakpoint( # now run some "continues" to show re-entries for _ in range(5): child.sendline('continue') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) before = str(child.before.decode()) assert "Attaching pdb to actor: ('breakpoint_forever'" in before @@ -415,7 +416,7 @@ def test_subactor_breakpoint( child.sendline('q') # child process should exit but parent will capture pdb.BdbQuit - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) before = str(child.before.decode()) assert "RemoteActorError: ('breakpoint_forever'" in before @@ -448,7 +449,7 @@ def test_multi_subactors( child = spawn(r'multi_subactors') # scan for the pdbpp prompt - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) before = str(child.before.decode()) assert "Attaching pdb to actor: ('breakpoint_forever'" in before @@ -460,7 +461,7 @@ def test_multi_subactors( # entries for _ in range(10): child.sendline('next') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) if ctlc: do_ctlc(child) @@ -469,7 +470,7 @@ def test_multi_subactors( child.sendline('c') # first name_error failure - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) before = str(child.before.decode()) assert "Attaching to pdb in crashed actor: ('name_error'" in before assert "NameError" in before @@ -481,7 +482,7 @@ def test_multi_subactors( child.sendline('c') # 2nd name_error failure - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) # TODO: will we ever get the race where this crash will show up? # blocklist strat now prevents this crash @@ -495,7 +496,7 @@ def test_multi_subactors( # breakpoint loop should re-engage child.sendline('c') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) before = str(child.before.decode()) assert "Attaching pdb to actor: ('breakpoint_forever'" in before @@ -511,7 +512,7 @@ def test_multi_subactors( ): child.sendline('c') time.sleep(0.1) - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) before = str(child.before.decode()) if ctlc: @@ -530,11 +531,11 @@ def test_multi_subactors( # now run some "continues" to show re-entries for _ in range(5): child.sendline('c') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) # quit the loop and expect parent to attach child.sendline('q') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) before = str(child.before.decode()) assert_before(child, [ @@ -578,7 +579,7 @@ def test_multi_daemon_subactors( ''' child = spawn('multi_daemon_subactors') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) # there can be a race for which subactor will acquire # the root's tty lock first so anticipate either crash @@ -608,7 +609,7 @@ def test_multi_daemon_subactors( # second entry by `bp_forever`. child.sendline('c') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) assert_before(child, [next_msg]) # XXX: hooray the root clobbering the child here was fixed! @@ -630,7 +631,7 @@ def test_multi_daemon_subactors( # expect another breakpoint actor entry child.sendline('c') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) try: assert_before(child, [bp_forever_msg]) @@ -646,7 +647,7 @@ def test_multi_daemon_subactors( # after 1 or more further bp actor entries. child.sendline('c') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) assert_before(child, [name_error_msg]) # wait for final error in root @@ -654,7 +655,7 @@ def test_multi_daemon_subactors( while True: try: child.sendline('c') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) assert_before( child, [bp_forever_msg] @@ -688,7 +689,7 @@ def test_multi_subactors_root_errors( child = spawn('multi_subactor_root_errors') # scan for the pdbpp prompt - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) # at most one subactor should attach before the root is cancelled before = str(child.before.decode()) @@ -703,7 +704,7 @@ def test_multi_subactors_root_errors( # due to block list strat from #337, this will no longer # propagate before the root errors and cancels the spawner sub-tree. - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) # only if the blocking condition doesn't kick in fast enough before = str(child.before.decode()) @@ -718,7 +719,7 @@ def test_multi_subactors_root_errors( do_ctlc(child) child.sendline('c') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) # check if the spawner crashed or was blocked from debug # and if this intermediary attached check the boxed error @@ -735,7 +736,7 @@ def test_multi_subactors_root_errors( do_ctlc(child) child.sendline('c') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) # expect a root actor crash assert_before(child, [ @@ -784,7 +785,7 @@ def test_multi_nested_subactors_error_through_nurseries( for send_char in itertools.cycle(['c', 'q']): try: - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) child.sendline(send_char) time.sleep(0.01) @@ -826,7 +827,7 @@ def test_root_nursery_cancels_before_child_releases_tty_lock( child = spawn('root_cancelled_but_child_is_in_tty_lock') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) before = str(child.before.decode()) assert "NameError: name 'doggypants' is not defined" in before @@ -841,7 +842,7 @@ def test_root_nursery_cancels_before_child_releases_tty_lock( for i in range(4): time.sleep(0.5) try: - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) except ( EOF, @@ -898,7 +899,7 @@ def test_root_cancels_child_context_during_startup( ''' child = spawn('fast_error_in_root_after_spawn') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) before = str(child.before.decode()) assert "AssertionError" in before @@ -915,7 +916,7 @@ def test_different_debug_mode_per_actor( ctlc: bool, ): child = spawn('per_actor_debug') - child.expect(r"\(Pdb\+\+\)") + child.expect(PROMPT) # only one actor should enter the debugger before = str(child.before.decode()) From 23cffbd94092270d567d0ac6d981ecaad87b7cf9 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Sat, 15 Apr 2023 19:48:52 -0400 Subject: [PATCH 03/15] Use multiline import for debug mod --- tractor/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tractor/__init__.py b/tractor/__init__.py index 731f3e9..12123a2 100644 --- a/tractor/__init__.py +++ b/tractor/__init__.py @@ -44,7 +44,10 @@ from ._exceptions import ( ModuleNotExposed, ContextCancelled, ) -from ._debug import breakpoint, post_mortem +from ._debug import ( + breakpoint, + post_mortem, +) from . import msg from ._root import ( run_daemon, From cc82447db6c3420a0e606356bd8d3110e2776aa8 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Sat, 15 Apr 2023 19:49:25 -0400 Subject: [PATCH 04/15] First try: switch debug machinery over to `pdbp` B) --- tractor/_debug.py | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/tractor/_debug.py b/tractor/_debug.py index 47a9a88..c3c3aed 100644 --- a/tractor/_debug.py +++ b/tractor/_debug.py @@ -37,6 +37,7 @@ from typing import ( ) from types import FrameType +import pdbp import tractor import trio from trio_typing import TaskStatus @@ -54,15 +55,16 @@ from ._exceptions import ( from ._ipc import Channel -try: - # wtf: only exported when installed in dev mode? - import pdbpp -except ImportError: - # pdbpp is installed in regular mode...it monkey patches stuff - import pdb - xpm = getattr(pdb, 'xpm', None) - assert xpm, "pdbpp is not installed?" # type: ignore - pdbpp = pdb +# TODO: we can drop this now yah? +# try: +# # wtf: only exported when installed in dev mode? +# import pdbp +# except ImportError: +# # pdbpp is installed in regular mode...it monkey patches stuff +# import pdb +# xpm = getattr(pdb, 'xpm', None) +# assert xpm, "pdbpp is not installed?" # type: ignore +# pdbpp = pdb log = get_logger(__name__) @@ -154,22 +156,22 @@ class Lock: cls.repl = None -class TractorConfig(pdbpp.DefaultConfig): +class TractorConfig(pdbp.DefaultConfig): ''' - Custom ``pdbpp`` goodness. + Custom ``pdbp`` goodness. ''' # use_pygments = True - # sticky_by_default = True + sticky_by_default = True enable_hidden_frames = False -class MultiActorPdb(pdbpp.Pdb): +class MultiActorPdb(pdbp.Pdb): ''' - Add teardown hooks to the regular ``pdbpp.Pdb``. + Add teardown hooks to the regular ``pdbp.Pdb``. ''' - # override the pdbpp config with our coolio one + # override the pdbp config with our coolio one DefaultConfig = TractorConfig # def preloop(self): @@ -313,7 +315,7 @@ async def lock_tty_for_child( ) -> str: ''' Lock the TTY in the root process of an actor tree in a new - inter-actor-context-task such that the ``pdbpp`` debugger console + inter-actor-context-task such that the ``pdbp`` debugger console can be mutex-allocated to the calling sub-actor for REPL control without interference by other processes / threads. @@ -433,7 +435,7 @@ async def wait_for_parent_stdin_hijack( def mk_mpdb() -> tuple[MultiActorPdb, Callable]: pdb = MultiActorPdb() - # signal.signal = pdbpp.hideframe(signal.signal) + # signal.signal = pdbp.hideframe(signal.signal) Lock.shield_sigint() @@ -583,7 +585,7 @@ async def _breakpoint( # # frame = sys._getframe() # # last_f = frame.f_back # # last_f.f_globals['__tracebackhide__'] = True - # # signal.signal = pdbpp.hideframe(signal.signal) + # # signal.signal = pdbp.hideframe(signal.signal) def shield_sigint_handler( @@ -743,7 +745,7 @@ def shield_sigint_handler( # https://github.com/goodboy/tractor/issues/130#issuecomment-663752040 # https://github.com/prompt-toolkit/python-prompt-toolkit/blob/c2c6af8a0308f9e5d7c0e28cb8a02963fe0ce07a/prompt_toolkit/patch_stdout.py - # XXX: lol, see ``pdbpp`` issue: + # XXX LEGACY: lol, see ``pdbpp`` issue: # https://github.com/pdbpp/pdbpp/issues/496 @@ -798,7 +800,7 @@ def _post_mortem( # https://github.com/pdbpp/pdbpp/issues/480 # TODO: help with a 3.10+ major release if/when it arrives. - pdbpp.xpm(Pdb=lambda: pdb) + pdbp.xpm(Pdb=lambda: pdb) post_mortem = partial( From 86aef5238d5d173457e3bf8cf9a803fe321536cd Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Sat, 15 Apr 2023 19:50:54 -0400 Subject: [PATCH 05/15] Hide actor nursery exit frame --- tractor/_supervise.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tractor/_supervise.py b/tractor/_supervise.py index 3085272..7f77784 100644 --- a/tractor/_supervise.py +++ b/tractor/_supervise.py @@ -302,7 +302,7 @@ async def _open_and_supervise_one_cancels_all_nursery( ) -> typing.AsyncGenerator[ActorNursery, None]: # TODO: yay or nay? - # __tracebackhide__ = True + __tracebackhide__ = True # the collection of errors retreived from spawned sub-actors errors: dict[tuple[str, str], BaseException] = {} From 705538398f07bd8956f3a011fbebf14bb978385a Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Wed, 19 Apr 2023 15:31:02 -0400 Subject: [PATCH 06/15] `pdbp`: turn off line truncating by default, fixes terminal resizing stuff --- tractor/_debug.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tractor/_debug.py b/tractor/_debug.py index c3c3aed..383abe2 100644 --- a/tractor/_debug.py +++ b/tractor/_debug.py @@ -158,13 +158,17 @@ class Lock: class TractorConfig(pdbp.DefaultConfig): ''' - Custom ``pdbp`` goodness. + Custom ``pdbp`` goodness :surfer: ''' # use_pygments = True sticky_by_default = True enable_hidden_frames = False + # much thanks @mdmintz for the hot tip! + # fixes line spacing issue when resizing terminal B) + truncate_long_lines = False + class MultiActorPdb(pdbp.Pdb): ''' From ae4ff5dc8d78a2f929bcb3613cff1981c5cfe6db Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 8 May 2023 12:02:42 -0400 Subject: [PATCH 07/15] pdbp: adding typing to config settings vars --- tractor/_debug.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tractor/_debug.py b/tractor/_debug.py index 383abe2..60d175a 100644 --- a/tractor/_debug.py +++ b/tractor/_debug.py @@ -161,13 +161,13 @@ class TractorConfig(pdbp.DefaultConfig): Custom ``pdbp`` goodness :surfer: ''' - # use_pygments = True - sticky_by_default = True - enable_hidden_frames = False + use_pygments: bool = True + sticky_by_default: bool = False + enable_hidden_frames: bool = False # much thanks @mdmintz for the hot tip! # fixes line spacing issue when resizing terminal B) - truncate_long_lines = False + truncate_long_lines: bool = False class MultiActorPdb(pdbp.Pdb): From 9ccd3a74b6d3f42de466876a1859722f91b32907 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Sat, 28 Jan 2023 18:56:15 -0500 Subject: [PATCH 08/15] More detailed preface description --- docs/README.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/README.rst b/docs/README.rst index f82f0f9..24f04c1 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -6,8 +6,14 @@ ``tractor`` is a `structured concurrent`_, multi-processing_ runtime built on trio_. -Fundamentally ``tractor`` gives you parallelism via ``trio``-"*actors*": -our nurseries_ let you spawn new Python processes which each run a ``trio`` +Fundamentally, ``tractor`` gives you parallelism via +``trio``-"*actors*": independent Python processes (aka +non-shared-memory threads) which maintain structured +concurrency (SC) *end-to-end* inside a *supervision tree*. + +Cross-process (and thus cross-host) SC is accomplished through the +combined use of our "actor nurseries_" and an "SC-transitive IPC +protocol" constructed on top of multiple Pythons each running a ``trio`` scheduled runtime - a call to ``trio.run()``. We believe the system adheres to the `3 axioms`_ of an "`actor model`_" From 87c6e09d6b453a05197580a75ecd07a79a59d022 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Sun, 14 May 2023 22:52:24 -0400 Subject: [PATCH 09/15] Switch readme links to point @ `pdbp` B) --- docs/README.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/README.rst b/docs/README.rst index 24f04c1..9dfe2f6 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -29,7 +29,8 @@ Features - **It's just** a ``trio`` API - *Infinitely nesteable* process trees - Builtin IPC streaming APIs with task fan-out broadcasting -- A (first ever?) "native" multi-core debugger UX for Python using `pdb++`_ +- A "native" multi-core debugger REPL using `pdbp`_ (a fork & fix of + `pdb++`_ thanks to @mdmintz!) - Support for a swappable, OS specific, process spawning layer - A modular transport stack, allowing for custom serialization (eg. with `msgspec`_), communications protocols, and environment specific IPC @@ -155,7 +156,7 @@ it **is a bug**. "Native" multi-process debugging -------------------------------- -Using the magic of `pdb++`_ and our internal IPC, we've +Using the magic of `pdbp`_ and our internal IPC, we've been able to create a native feeling debugging experience for any (sub-)process in your ``tractor`` tree. @@ -603,6 +604,7 @@ channel`_! .. _adherance to: https://www.youtube.com/watch?v=7erJ1DV_Tlo&t=1821s .. _trio gitter channel: https://gitter.im/python-trio/general .. _matrix channel: https://matrix.to/#/!tractor:matrix.org +.. _pdbp: https://github.com/mdmintz/pdbp .. _pdb++: https://github.com/pdbpp/pdbpp .. _guest mode: https://trio.readthedocs.io/en/stable/reference-lowlevel.html?highlight=guest%20mode#using-guest-mode-to-run-trio-on-top-of-other-event-loops .. _messages: https://en.wikipedia.org/wiki/Message_passing From 95535b2226af1fd0ae68c7715ed1a9c4f83f1a25 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Tue, 7 Mar 2023 17:37:36 -0500 Subject: [PATCH 10/15] Some more 3.10+ optional type sigs --- tractor/_debug.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tractor/_debug.py b/tractor/_debug.py index 60d175a..59e1c76 100644 --- a/tractor/_debug.py +++ b/tractor/_debug.py @@ -754,8 +754,8 @@ def shield_sigint_handler( def _set_trace( - actor: Optional[tractor.Actor] = None, - pdb: Optional[MultiActorPdb] = None, + actor: tractor.Actor | None = None, + pdb: MultiActorPdb | None = None, ): __tracebackhide__ = True actor = actor or tractor.current_actor() @@ -765,7 +765,11 @@ def _set_trace( if frame: frame = frame.f_back # type: ignore - if frame and pdb and actor is not None: + if ( + frame + and pdb + and actor is not None + ): log.pdb(f"\nAttaching pdb to actor: {actor.uid}\n") # no f!#$&* idea, but when we're in async land # we need 2x frames up? @@ -774,7 +778,8 @@ def _set_trace( else: pdb, undo_sigint = mk_mpdb() - # we entered the global ``breakpoint()`` built-in from sync code? + # we entered the global ``breakpoint()`` built-in from sync + # code? Lock.local_task_in_debug = 'sync' pdb.set_trace(frame=frame) From 79622bbeeae86412ce718e5c21fdf198db559893 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Tue, 7 Mar 2023 16:46:14 -0500 Subject: [PATCH 11/15] Restore `breakpoint()` hook after runtime exits Previously we were leaking our (pdb++) override into the Python runtime which would always result in a runtime error whenever `breakpoint()` is called outside our runtime; after exit of the root actor . This explicitly restores any previous hook override (detected during startup) or deletes the hook and restores the environment if none existed prior. Also adds a new WIP debugging example script to ensure breakpointing works as normal after runtime close; this will be added to the test suite. --- .../debugging/restore_builtin_breakpoint.py | 24 +++++++++++++++++++ tractor/_root.py | 18 +++++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 examples/debugging/restore_builtin_breakpoint.py diff --git a/examples/debugging/restore_builtin_breakpoint.py b/examples/debugging/restore_builtin_breakpoint.py new file mode 100644 index 0000000..6e141df --- /dev/null +++ b/examples/debugging/restore_builtin_breakpoint.py @@ -0,0 +1,24 @@ +import os +import sys + +import trio +import tractor + + +async def main() -> None: + async with tractor.open_nursery(debug_mode=True) as an: + + assert os.environ['PYTHONBREAKPOINT'] == 'tractor._debug._set_trace' + + # TODO: an assert that verifies the hook has indeed been, hooked + # XD + assert sys.breakpointhook is not tractor._debug._set_trace + + breakpoint() + + # TODO: an assert that verifies the hook is unhooked.. + assert sys.breakpointhook + breakpoint() + +if __name__ == '__main__': + trio.run(main) diff --git a/tractor/_root.py b/tractor/_root.py index 840b288..64652a1 100644 --- a/tractor/_root.py +++ b/tractor/_root.py @@ -22,8 +22,9 @@ from contextlib import asynccontextmanager from functools import partial import importlib import logging -import os import signal +import sys +import os import typing import warnings @@ -84,8 +85,10 @@ async def open_root_actor( ''' # Override the global debugger hook to make it play nice with - # ``trio``, see: + # ``trio``, see much discussion in: # https://github.com/python-trio/trio/issues/1155#issuecomment-742964018 + builtin_bp_handler = sys.breakpointhook + orig_bp_path: str | None = os.environ.get('PYTHONBREAKPOINT', None) os.environ['PYTHONBREAKPOINT'] = 'tractor._debug._set_trace' # attempt to retreive ``trio``'s sigint handler and stash it @@ -254,6 +257,15 @@ async def open_root_actor( await actor.cancel() finally: _state._current_actor = None + + # restore breakpoint hook state + sys.breakpointhook = builtin_bp_handler + if orig_bp_path is not None: + os.environ['PYTHONBREAKPOINT'] = orig_bp_path + else: + # clear env back to having no entry + os.environ.pop('PYTHONBREAKPOINT') + logger.runtime("Root actor terminated") @@ -289,7 +301,7 @@ def run_daemon( async def _main(): async with open_root_actor( - arbiter_addr=registry_addr, + registry_addr=registry_addr, name=name, start_method=start_method, debug_mode=debug_mode, From 73befac9bc3d4be5ae94fff3b44287b8bf0b31ff Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 15 May 2023 09:01:27 -0400 Subject: [PATCH 12/15] Switch to `pdbp` in test reqs --- requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-test.txt b/requirements-test.txt index 579b6f0..8070f2c 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,7 +1,7 @@ pytest pytest-trio pytest-timeout -pdbpp +pdbp mypy trio_typing pexpect From 1c3893a383b889ad4f16abd06985ce700ab991c9 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 15 May 2023 09:01:55 -0400 Subject: [PATCH 13/15] Drop commented `pdbpp` import logic --- tractor/_debug.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tractor/_debug.py b/tractor/_debug.py index 59e1c76..b0482f1 100644 --- a/tractor/_debug.py +++ b/tractor/_debug.py @@ -54,18 +54,6 @@ from ._exceptions import ( ) from ._ipc import Channel - -# TODO: we can drop this now yah? -# try: -# # wtf: only exported when installed in dev mode? -# import pdbp -# except ImportError: -# # pdbpp is installed in regular mode...it monkey patches stuff -# import pdb -# xpm = getattr(pdb, 'xpm', None) -# assert xpm, "pdbpp is not installed?" # type: ignore -# pdbpp = pdb - log = get_logger(__name__) From 6758e4487c81067ccdee65c542a2cb38bc436b51 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 15 May 2023 09:12:13 -0400 Subject: [PATCH 14/15] Drop lingering `pdbpp` comment-refs in tests --- tests/test_debugger.py | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/tests/test_debugger.py b/tests/test_debugger.py index 5f90703..a44a313 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -151,19 +151,6 @@ def ctlc( use_ctlc = request.param - # TODO: we can remove this bc pdbp right? - if ( - sys.version_info <= (3, 10) - and use_ctlc - ): - # on 3.9 it seems the REPL UX - # is highly unreliable and frankly annoying - # to test for. It does work from manual testing - # but i just don't think it's wroth it to try - # and get this working especially since we want to - # be 3.10+ mega-asap. - pytest.skip('Py3.9 and `pdbpp` son no bueno..') - node = request.node markers = node.own_markers for mark in markers: @@ -194,13 +181,15 @@ def ctlc( ids=lambda item: f'{item[0]} -> {item[1]}', ) def test_root_actor_error(spawn, user_in_out): - """Demonstrate crash handler entering pdbpp from basic error in root actor. - """ + ''' + Demonstrate crash handler entering pdb from basic error in root actor. + + ''' user_input, expect_err_str = user_in_out child = spawn('root_actor_error') - # scan for the pdbpp prompt + # scan for the prompt expect(child, PROMPT) before = str(child.before.decode()) @@ -231,7 +220,7 @@ def test_root_actor_bp(spawn, user_in_out): user_input, expect_err_str = user_in_out child = spawn('root_actor_breakpoint') - # scan for the pdbpp prompt + # scan for the prompt child.expect(PROMPT) assert 'Error' not in str(child.before) @@ -339,7 +328,7 @@ def test_subactor_error( ''' child = spawn('subactor_error') - # scan for the pdbpp prompt + # scan for the prompt child.expect(PROMPT) before = str(child.before.decode()) @@ -387,7 +376,7 @@ def test_subactor_breakpoint( child = spawn('subactor_breakpoint') - # scan for the pdbpp prompt + # scan for the prompt child.expect(PROMPT) before = str(child.before.decode()) @@ -448,7 +437,7 @@ def test_multi_subactors( ''' child = spawn(r'multi_subactors') - # scan for the pdbpp prompt + # scan for the prompt child.expect(PROMPT) before = str(child.before.decode()) @@ -688,7 +677,7 @@ def test_multi_subactors_root_errors( ''' child = spawn('multi_subactor_root_errors') - # scan for the pdbpp prompt + # scan for the prompt child.expect(PROMPT) # at most one subactor should attach before the root is cancelled From 41aa91c8ebdff6572e64925c0b9a4ab6c08dc147 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 15 May 2023 09:35:59 -0400 Subject: [PATCH 15/15] Add news file --- nooz/358.feature.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 nooz/358.feature.rst diff --git a/nooz/358.feature.rst b/nooz/358.feature.rst new file mode 100644 index 0000000..80b8367 --- /dev/null +++ b/nooz/358.feature.rst @@ -0,0 +1,15 @@ +Switch to using the fork & fix of `pdb++`, `pdbp`: +https://github.com/mdmintz/pdbp + +Allows us to sidestep a variety of issues that aren't being maintained +in the upstream project thanks to the hard work of @mdmintz! + +We also include some default settings adjustments as per recent +development on the fork: + +- sticky mode is still turned on by default but now activates when + a using the `ll` repl command. +- turn off line truncation by default to avoid inter-line gaps when + resizing the terimnal during use. +- when using the backtrace cmd either by `w` or `bt`, the config + automatically switches to non-sticky mode.