forked from goodboy/tractor
				
			Fix `.devx.maybe_wait_for_debugger()` polling deats
When entered by the root actor avoid excessive polling cycles by, - blocking on the `Lock.no_remote_has_tty: trio.Event` and breaking *immediately* when set (though we should really also lock it from the root right?) to avoid extra loops.. - shielding the `await trio.sleep(poll_delay)` call to avoid any local cancellation causing the (presumably root-actor task) caller to move on (possibly to cancel its children) and instead to continue poll-blocking until the lock is actually released by its user. - `break` the poll loop immediately if no remote locker is detected. - use `.pdb()` level for reporting lock state changes. Also add a #TODO to handle calls by non-root actors as it pertains toremotes/1757153874605917753/main
							parent
							
								
									199ca48cc4
								
							
						
					
					
						commit
						1fb4d7318b
					
				|  | @ -1,18 +1,19 @@ | |||
| # tractor: structured concurrent "actors". | ||||
| # Copyright 2018-eternity Tyler Goodlet. | ||||
| 
 | ||||
| # This program is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU Affero General Public License as published by | ||||
| # the Free Software Foundation, either version 3 of the License, or | ||||
| # (at your option) any later version. | ||||
| # This program is free software: you can redistribute it and/or | ||||
| # modify it under the terms of the GNU Affero General Public License | ||||
| # as published by the Free Software Foundation, either version 3 of | ||||
| # the License, or (at your option) any later version. | ||||
| 
 | ||||
| # This program is distributed in the hope that it will be useful, | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU Affero General Public License for more details. | ||||
| # This program is distributed in the hope that it will be useful, but | ||||
| # WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
| # Affero General Public License for more details. | ||||
| 
 | ||||
| # You should have received a copy of the GNU Affero General Public License | ||||
| # along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
| # You should have received a copy of the GNU Affero General Public | ||||
| # License along with this program.  If not, see | ||||
| # <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| """ | ||||
| Multi-core debugging for da peeps! | ||||
|  | @ -43,6 +44,7 @@ from types import FrameType | |||
| import pdbp | ||||
| import tractor | ||||
| import trio | ||||
| from trio.lowlevel import current_task | ||||
| from trio_typing import ( | ||||
|     TaskStatus, | ||||
|     # Task, | ||||
|  | @ -50,6 +52,7 @@ from trio_typing import ( | |||
| 
 | ||||
| from ..log import get_logger | ||||
| from .._state import ( | ||||
|     current_actor, | ||||
|     is_root_process, | ||||
|     debug_mode, | ||||
| ) | ||||
|  | @ -238,7 +241,7 @@ async def _acquire_debug_lock_from_root_task( | |||
|     to the ``pdb`` repl. | ||||
| 
 | ||||
|     ''' | ||||
|     task_name: str = trio.lowlevel.current_task().name | ||||
|     task_name: str = current_task().name | ||||
|     we_acquired: bool = False | ||||
| 
 | ||||
|     log.runtime( | ||||
|  | @ -323,8 +326,7 @@ async def lock_tty_for_child( | |||
|     highly reliable at releasing the mutex complete! | ||||
| 
 | ||||
|     ''' | ||||
|     task_name = trio.lowlevel.current_task().name | ||||
| 
 | ||||
|     task_name: str = current_task().name | ||||
|     if tuple(subactor_uid) in Lock._blocked: | ||||
|         log.warning( | ||||
|             f'Actor {subactor_uid} is blocked from acquiring debug lock\n' | ||||
|  | @ -407,11 +409,13 @@ async def wait_for_parent_stdin_hijack( | |||
|                     assert val == 'Locked' | ||||
| 
 | ||||
|                     async with ctx.open_stream() as stream: | ||||
|                         # unblock local caller | ||||
| 
 | ||||
|                         try: | ||||
|                             # unblock local caller | ||||
|                             assert Lock.local_pdb_complete | ||||
|                             task_status.started(cs) | ||||
| 
 | ||||
|                             # wait for local task to exit and | ||||
|                             # release the REPL | ||||
|                             await Lock.local_pdb_complete.wait() | ||||
| 
 | ||||
|                         finally: | ||||
|  | @ -468,7 +472,7 @@ def shield_sigint_handler( | |||
| 
 | ||||
|     uid_in_debug: tuple[str, str] | None = Lock.global_actor_in_debug | ||||
| 
 | ||||
|     actor = tractor.current_actor() | ||||
|     actor = current_actor() | ||||
|     # print(f'{actor.uid} in HANDLER with ') | ||||
| 
 | ||||
|     def do_cancel(): | ||||
|  | @ -613,7 +617,7 @@ def _set_trace( | |||
|     shield: bool = False, | ||||
| ): | ||||
|     __tracebackhide__: bool = True | ||||
|     actor: tractor.Actor = actor or tractor.current_actor() | ||||
|     actor: tractor.Actor = actor or current_actor() | ||||
| 
 | ||||
|     # start 2 levels up in user code | ||||
|     frame: FrameType | None = sys._getframe() | ||||
|  | @ -683,9 +687,9 @@ async def pause( | |||
| 
 | ||||
|     ''' | ||||
|     # __tracebackhide__ = True | ||||
|     actor = tractor.current_actor() | ||||
|     actor = current_actor() | ||||
|     pdb, undo_sigint = mk_mpdb() | ||||
|     task_name = trio.lowlevel.current_task().name | ||||
|     task_name: str = trio.lowlevel.current_task().name | ||||
| 
 | ||||
|     if ( | ||||
|         not Lock.local_pdb_complete | ||||
|  | @ -836,7 +840,7 @@ async def pause( | |||
| # runtime aware version which takes care of all . | ||||
| def pause_from_sync() -> None: | ||||
|     print("ENTER SYNC PAUSE") | ||||
|     actor: tractor.Actor = tractor.current_actor( | ||||
|     actor: tractor.Actor = current_actor( | ||||
|         err_on_no_runtime=False, | ||||
|     ) | ||||
|     if actor: | ||||
|  | @ -971,9 +975,10 @@ async def acquire_debug_lock( | |||
|     ''' | ||||
|     Grab root's debug lock on entry, release on exit. | ||||
| 
 | ||||
|     This helper is for actor's who don't actually need | ||||
|     to acquired the debugger but want to wait until the | ||||
|     lock is free in the process-tree root. | ||||
|     This helper is for actor's who don't actually need to acquired | ||||
|     the debugger but want to wait until the lock is free in the | ||||
|     process-tree root such that they don't clobber an ongoing pdb | ||||
|     REPL session in some peer or child! | ||||
| 
 | ||||
|     ''' | ||||
|     if not debug_mode(): | ||||
|  | @ -1013,43 +1018,71 @@ async def maybe_wait_for_debugger( | |||
|         # tearing down. | ||||
|         sub_in_debug: tuple[str, str] | None = None | ||||
| 
 | ||||
|         for _ in range(poll_steps): | ||||
|         for istep in range(poll_steps): | ||||
| 
 | ||||
|             if Lock.global_actor_in_debug: | ||||
|                 sub_in_debug = tuple(Lock.global_actor_in_debug) | ||||
| 
 | ||||
|             log.debug('Root polling for debug') | ||||
| 
 | ||||
|             with trio.CancelScope(shield=True): | ||||
|                 await trio.sleep(poll_delay) | ||||
| 
 | ||||
|                 # TODO: could this make things more deterministic?  wait | ||||
|                 # to see if a sub-actor task will be scheduled and grab | ||||
|                 # the tty lock on the next tick? | ||||
|                 # XXX: doesn't seem to work | ||||
|             if sub_in_debug := Lock.global_actor_in_debug: | ||||
|                 log.pdb( | ||||
|                     f'Lock in use by {sub_in_debug}' | ||||
|                 ) | ||||
|                 # TODO: could this make things more deterministic? | ||||
|                 # wait to see if a sub-actor task will be | ||||
|                 # scheduled and grab the tty lock on the next | ||||
|                 # tick? | ||||
|                 # XXX => but it doesn't seem to work.. | ||||
|                 # await trio.testing.wait_all_tasks_blocked(cushion=0) | ||||
| 
 | ||||
|                 debug_complete = Lock.no_remote_has_tty | ||||
|                 if ( | ||||
|                     debug_complete | ||||
|                     and sub_in_debug is not None | ||||
|                     and not debug_complete.is_set() | ||||
|                 ): | ||||
|                     log.pdb( | ||||
|                         'Root has errored but pdb is in use by ' | ||||
|                         f'child {sub_in_debug}\n' | ||||
|                         'Waiting on tty lock to release..' | ||||
|                     ) | ||||
|             debug_complete: trio.Event|None = Lock.no_remote_has_tty | ||||
|             if ( | ||||
|                 debug_complete | ||||
|                 and not debug_complete.is_set() | ||||
|                 and sub_in_debug is not None | ||||
|             ): | ||||
|                 log.pdb( | ||||
|                     'Root has errored but pdb is in use by child\n' | ||||
|                     'Waiting on tty lock to release..\n' | ||||
|                     f'uid: {sub_in_debug}\n' | ||||
|                 ) | ||||
|                 await debug_complete.wait() | ||||
|                 log.pdb( | ||||
|                     f'Child subactor released debug lock!\n' | ||||
|                     f'uid: {sub_in_debug}\n' | ||||
|                 ) | ||||
|                 if debug_complete.is_set(): | ||||
|                     break | ||||
| 
 | ||||
|                     await debug_complete.wait() | ||||
|             # is no subactor locking debugger currently? | ||||
|             elif ( | ||||
|                 debug_complete is None | ||||
|                 or sub_in_debug is None | ||||
|             ): | ||||
|                 log.pdb( | ||||
|                     'Root acquired debug TTY LOCK from child\n' | ||||
|                     f'uid: {sub_in_debug}' | ||||
|                 ) | ||||
|                 break | ||||
| 
 | ||||
|                 await trio.sleep(poll_delay) | ||||
|                 continue | ||||
|             else: | ||||
|                 # TODO: don't need this right? | ||||
|                 # await trio.lowlevel.checkpoint() | ||||
| 
 | ||||
|                 log.debug( | ||||
|                     'Root polling for debug:\n' | ||||
|                     f'poll step: {istep}\n' | ||||
|                     f'poll delya: {poll_delay}' | ||||
|                 ) | ||||
|                 with trio.CancelScope(shield=True): | ||||
|                     await trio.sleep(poll_delay) | ||||
|                     continue | ||||
|         else: | ||||
|             log.debug( | ||||
|                     'Root acquired TTY LOCK' | ||||
|             ) | ||||
|             log.pdb('Root acquired debug TTY LOCK') | ||||
| 
 | ||||
|     # else: | ||||
|     #     # TODO: non-root call for #320? | ||||
|     #     this_uid: tuple[str, str] = current_actor().uid | ||||
|     #     async with acquire_debug_lock( | ||||
|     #         subactor_uid=this_uid, | ||||
|     #     ): | ||||
|     #         pass | ||||
| 
 | ||||
| # TODO: better naming and what additionals? | ||||
| # - [ ] optional runtime plugging? | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue