commit
						e5ee2e3de8
					
				|  | @ -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`_" | ||||
|  | @ -23,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 | ||||
|  | @ -149,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. | ||||
| 
 | ||||
|  | @ -597,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 | ||||
|  |  | |||
|  | @ -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) | ||||
|  | @ -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. | ||||
|  | @ -1,7 +1,7 @@ | |||
| pytest | ||||
| pytest-trio | ||||
| pytest-timeout | ||||
| pdbpp | ||||
| pdbp | ||||
| mypy | ||||
| trio_typing | ||||
| pexpect | ||||
|  |  | |||
							
								
								
									
										13
									
								
								setup.py
								
								
								
								
							
							
						
						
									
										13
									
								
								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', | ||||
|  |  | |||
|  | @ -95,7 +95,7 @@ def spawn( | |||
|     return _spawn | ||||
| 
 | ||||
| 
 | ||||
| PROMPT = r"\(Pdb\+\+\)" | ||||
| PROMPT = r"\(Pdb\+\)" | ||||
| 
 | ||||
| 
 | ||||
| def expect( | ||||
|  | @ -151,18 +151,6 @@ def ctlc( | |||
| 
 | ||||
|     use_ctlc = request.param | ||||
| 
 | ||||
|     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: | ||||
|  | @ -193,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()) | ||||
|  | @ -230,8 +220,8 @@ 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 | ||||
|     child.expect(r"\(Pdb\+\+\)") | ||||
|     # scan for the prompt | ||||
|     child.expect(PROMPT) | ||||
| 
 | ||||
|     assert 'Error' not in str(child.before) | ||||
| 
 | ||||
|  | @ -272,7 +262,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 +281,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 +291,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 +302,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') | ||||
|  | @ -338,8 +328,8 @@ def test_subactor_error( | |||
|     ''' | ||||
|     child = spawn('subactor_error') | ||||
| 
 | ||||
|     # scan for the pdbpp prompt | ||||
|     child.expect(r"\(Pdb\+\+\)") | ||||
|     # scan for the prompt | ||||
|     child.expect(PROMPT) | ||||
| 
 | ||||
|     before = str(child.before.decode()) | ||||
|     assert "Attaching to pdb in crashed actor: ('name_error'" in before | ||||
|  | @ -359,7 +349,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 | ||||
|  | @ -386,8 +376,8 @@ def test_subactor_breakpoint( | |||
| 
 | ||||
|     child = spawn('subactor_breakpoint') | ||||
| 
 | ||||
|     # scan for the pdbpp prompt | ||||
|     child.expect(r"\(Pdb\+\+\)") | ||||
|     # scan for the prompt | ||||
|     child.expect(PROMPT) | ||||
| 
 | ||||
|     before = str(child.before.decode()) | ||||
|     assert "Attaching pdb to actor: ('breakpoint_forever'" in before | ||||
|  | @ -396,7 +386,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 +394,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 +405,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 | ||||
|  | @ -447,8 +437,8 @@ def test_multi_subactors( | |||
|     ''' | ||||
|     child = spawn(r'multi_subactors') | ||||
| 
 | ||||
|     # scan for the pdbpp prompt | ||||
|     child.expect(r"\(Pdb\+\+\)") | ||||
|     # scan for the prompt | ||||
|     child.expect(PROMPT) | ||||
| 
 | ||||
|     before = str(child.before.decode()) | ||||
|     assert "Attaching pdb to actor: ('breakpoint_forever'" in before | ||||
|  | @ -460,7 +450,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 +459,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 +471,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 +485,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 +501,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 +520,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 +568,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 +598,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 +620,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 +636,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 +644,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] | ||||
|  | @ -687,8 +677,8 @@ def test_multi_subactors_root_errors( | |||
|     ''' | ||||
|     child = spawn('multi_subactor_root_errors') | ||||
| 
 | ||||
|     # scan for the pdbpp prompt | ||||
|     child.expect(r"\(Pdb\+\+\)") | ||||
|     # scan for the prompt | ||||
|     child.expect(PROMPT) | ||||
| 
 | ||||
|     # at most one subactor should attach before the root is cancelled | ||||
|     before = str(child.before.decode()) | ||||
|  | @ -703,7 +693,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 +708,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 +725,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 +774,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 +816,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 +831,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 +888,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 +905,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()) | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -37,6 +37,7 @@ from typing import ( | |||
| ) | ||||
| from types import FrameType | ||||
| 
 | ||||
| import pdbp | ||||
| import tractor | ||||
| import trio | ||||
| from trio_typing import TaskStatus | ||||
|  | @ -53,17 +54,6 @@ 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 | ||||
| 
 | ||||
| log = get_logger(__name__) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -154,22 +144,26 @@ class Lock: | |||
|             cls.repl = None | ||||
| 
 | ||||
| 
 | ||||
| class TractorConfig(pdbpp.DefaultConfig): | ||||
| class TractorConfig(pdbp.DefaultConfig): | ||||
|     ''' | ||||
|     Custom ``pdbpp`` goodness. | ||||
|     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: bool = 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 +307,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 +427,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 +577,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,13 +737,13 @@ 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 | ||||
| 
 | ||||
| 
 | ||||
| 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() | ||||
|  | @ -759,7 +753,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? | ||||
|  | @ -768,7 +766,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) | ||||
|  | @ -798,7 +797,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( | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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] = {} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue