Compare commits
	
		
			6 Commits 
		
	
	
		
			93f70c63a4
			...
			9a70a214c1
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								 | 
						9a70a214c1 | |
| 
							
							
								 | 
						2bd8bf16d7 | |
| 
							
							
								 | 
						1d8230716c | |
| 
							
							
								 | 
						df8e326e39 | |
| 
							
							
								 | 
						13dbd1d420 | |
| 
							
							
								 | 
						b2c415c4f6 | 
| 
						 | 
				
			
			@ -24,10 +24,9 @@ async def spawn_until(depth=0):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
async def main():
 | 
			
		||||
    """The main ``tractor`` routine.
 | 
			
		||||
 | 
			
		||||
    The process tree should look as approximately as follows when the debugger
 | 
			
		||||
    first engages:
 | 
			
		||||
    '''
 | 
			
		||||
    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 ...)
 | 
			
		||||
| 
						 | 
				
			
			@ -37,10 +36,11 @@ async def main():
 | 
			
		|||
    └─ python -m tractor._child --uid ('spawner0', '1d42012b ...)
 | 
			
		||||
       └─ python -m tractor._child --uid ('name_error', '6c2733b8 ...)
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    '''
 | 
			
		||||
    async with tractor.open_nursery(
 | 
			
		||||
        debug_mode=True,
 | 
			
		||||
        loglevel='warning'
 | 
			
		||||
        loglevel='devx',
 | 
			
		||||
        enable_transports=['uds'],
 | 
			
		||||
    ) as n:
 | 
			
		||||
 | 
			
		||||
        # spawn both actors
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,6 +37,7 @@ async def main(
 | 
			
		|||
            enable_stack_on_sig=True,
 | 
			
		||||
            # maybe_enable_greenback=False,
 | 
			
		||||
            loglevel='devx',
 | 
			
		||||
            enable_transports=['uds'],
 | 
			
		||||
        ) as an,
 | 
			
		||||
    ):
 | 
			
		||||
        ptl: tractor.Portal  = await an.start_actor(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,8 +33,11 @@ async def just_bp(
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
async def main():
 | 
			
		||||
 | 
			
		||||
    async with tractor.open_nursery(
 | 
			
		||||
        debug_mode=True,
 | 
			
		||||
        enable_transports=['uds'],
 | 
			
		||||
        loglevel='devx',
 | 
			
		||||
    ) as n:
 | 
			
		||||
        p = await n.start_actor(
 | 
			
		||||
            'bp_boi',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,10 +10,14 @@ TODO:
 | 
			
		|||
    - wonder if any of it'll work on OS X?
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from __future__ import annotations
 | 
			
		||||
from functools import partial
 | 
			
		||||
import itertools
 | 
			
		||||
import platform
 | 
			
		||||
import time
 | 
			
		||||
from typing import (
 | 
			
		||||
    TYPE_CHECKING,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
from pexpect.exceptions import (
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +38,9 @@ from .conftest import (
 | 
			
		|||
    assert_before,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from ..conftest import PexpectSpawner
 | 
			
		||||
 | 
			
		||||
# TODO: The next great debugger audit could be done by you!
 | 
			
		||||
# - recurrent entry to breakpoint() from single actor *after* and an
 | 
			
		||||
#   error in another task?
 | 
			
		||||
| 
						 | 
				
			
			@ -1062,6 +1069,88 @@ def test_shield_pause(
 | 
			
		|||
    child.expect(EOF)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize(
 | 
			
		||||
    'quit_early', [False, True]
 | 
			
		||||
)
 | 
			
		||||
def test_ctxep_pauses_n_maybe_ipc_breaks(
 | 
			
		||||
    spawn: PexpectSpawner,
 | 
			
		||||
    quit_early: bool,
 | 
			
		||||
):
 | 
			
		||||
    '''
 | 
			
		||||
    Audit generator embedded `.pause()`es from within a `@context`
 | 
			
		||||
    endpoint with a chan close at the end, requiring that ctl-c is
 | 
			
		||||
    mashed and zombie reaper kills sub with no hangs.
 | 
			
		||||
 | 
			
		||||
    '''
 | 
			
		||||
    child = spawn('subactor_bp_in_ctx')
 | 
			
		||||
    child.expect(PROMPT)
 | 
			
		||||
 | 
			
		||||
    # 3 iters for the `gen()` pause-points
 | 
			
		||||
    for i in range(3):
 | 
			
		||||
        assert_before(
 | 
			
		||||
            child,
 | 
			
		||||
            [
 | 
			
		||||
                _pause_msg,
 | 
			
		||||
                "('bp_boi'",  # actor name
 | 
			
		||||
                "<Task 'just_bp'",  # task name
 | 
			
		||||
            ]
 | 
			
		||||
        )
 | 
			
		||||
        if (
 | 
			
		||||
            i == 1
 | 
			
		||||
            and
 | 
			
		||||
            quit_early
 | 
			
		||||
        ):
 | 
			
		||||
            child.sendline('q')
 | 
			
		||||
            child.expect(PROMPT)
 | 
			
		||||
            assert_before(
 | 
			
		||||
                child,
 | 
			
		||||
                ["tractor._exceptions.RemoteActorError: remote task raised a 'BdbQuit'",
 | 
			
		||||
                 "bdb.BdbQuit",
 | 
			
		||||
                 "('bp_boi'",
 | 
			
		||||
                ]
 | 
			
		||||
            )
 | 
			
		||||
            child.sendline('c')
 | 
			
		||||
            child.expect(EOF)
 | 
			
		||||
            assert_before(
 | 
			
		||||
                child,
 | 
			
		||||
                ["tractor._exceptions.RemoteActorError: remote task raised a 'BdbQuit'",
 | 
			
		||||
                 "bdb.BdbQuit",
 | 
			
		||||
                 "('bp_boi'",
 | 
			
		||||
                ]
 | 
			
		||||
            )
 | 
			
		||||
            break  # end-of-test
 | 
			
		||||
 | 
			
		||||
        child.sendline('c')
 | 
			
		||||
        try:
 | 
			
		||||
            child.expect(PROMPT)
 | 
			
		||||
        except TIMEOUT:
 | 
			
		||||
            # no prompt since we hang due to IPC chan purposely
 | 
			
		||||
            # closed so verify we see error reporting as well as
 | 
			
		||||
            # a failed crash-REPL request msg and can CTL-c our way
 | 
			
		||||
            # out.
 | 
			
		||||
            assert_before(
 | 
			
		||||
                child,
 | 
			
		||||
                ['peer IPC channel closed abruptly?',
 | 
			
		||||
                 'another task closed this fd',
 | 
			
		||||
                 'Debug lock request was CANCELLED?',
 | 
			
		||||
                 "TransportClosed: 'MsgpackUDSStream' was already closed locally ?",]
 | 
			
		||||
 | 
			
		||||
                # XXX races on whether these show/hit?
 | 
			
		||||
                 # 'Failed to REPl via `_pause()` You called `tractor.pause()` from an already cancelled scope!',
 | 
			
		||||
                 # 'AssertionError',
 | 
			
		||||
            )
 | 
			
		||||
            # OSc(ancel) the hanging tree
 | 
			
		||||
            do_ctlc(
 | 
			
		||||
                child=child,
 | 
			
		||||
                expect_prompt=False,
 | 
			
		||||
            )
 | 
			
		||||
            child.expect(EOF)
 | 
			
		||||
            assert_before(
 | 
			
		||||
                child,
 | 
			
		||||
                ['KeyboardInterrupt'],
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# TODO: better error for "non-ideal" usage from the root actor.
 | 
			
		||||
# -[ ] if called from an async scope emit a message that suggests
 | 
			
		||||
#    using `await tractor.pause()` instead since it's less overhead
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -135,12 +135,12 @@ def _trio_main(
 | 
			
		|||
            f'  loglevel: {actor.loglevel}\n'
 | 
			
		||||
        )
 | 
			
		||||
        log.info(
 | 
			
		||||
            'Starting new `trio` subactor:\n'
 | 
			
		||||
            'Starting new `trio` subactor\n'
 | 
			
		||||
            +
 | 
			
		||||
            pformat.nest_from_op(
 | 
			
		||||
                input_op='>(',  # see syntax ideas above
 | 
			
		||||
                tree_str=actor_info,
 | 
			
		||||
                back_from_op=2,  # since "complete"
 | 
			
		||||
                text=actor_info,
 | 
			
		||||
                nest_indent=2,  # since "complete"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    logmeth = log.info
 | 
			
		||||
| 
						 | 
				
			
			@ -149,8 +149,8 @@ def _trio_main(
 | 
			
		|||
        +
 | 
			
		||||
        pformat.nest_from_op(
 | 
			
		||||
            input_op=')>',  # like a "closed-to-play"-icon from super perspective
 | 
			
		||||
            tree_str=actor_info,
 | 
			
		||||
            back_from_op=1,
 | 
			
		||||
            text=actor_info,
 | 
			
		||||
            nest_indent=1,
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    try:
 | 
			
		||||
| 
						 | 
				
			
			@ -167,7 +167,7 @@ def _trio_main(
 | 
			
		|||
            +
 | 
			
		||||
            pformat.nest_from_op(
 | 
			
		||||
                input_op='c)>',  # closed due to cancel (see above)
 | 
			
		||||
                tree_str=actor_info,
 | 
			
		||||
                text=actor_info,
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    except BaseException as err:
 | 
			
		||||
| 
						 | 
				
			
			@ -177,7 +177,7 @@ def _trio_main(
 | 
			
		|||
            +
 | 
			
		||||
            pformat.nest_from_op(
 | 
			
		||||
                input_op='x)>',  # closed by error
 | 
			
		||||
                tree_str=actor_info,
 | 
			
		||||
                text=actor_info,
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        # NOTE since we raise a tb will already be shown on the
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -521,7 +521,7 @@ async def open_root_actor(
 | 
			
		|||
 | 
			
		||||
                    op_nested_actor_repr: str = _pformat.nest_from_op(
 | 
			
		||||
                        input_op='>) ',
 | 
			
		||||
                        tree_str=actor.pformat(),
 | 
			
		||||
                        text=actor.pformat(),
 | 
			
		||||
                        nest_prefix='|_',
 | 
			
		||||
                    )
 | 
			
		||||
                    logger.info(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -255,7 +255,7 @@ async def _errors_relayed_via_ipc(
 | 
			
		|||
    ctx: Context,
 | 
			
		||||
    is_rpc: bool,
 | 
			
		||||
 | 
			
		||||
    hide_tb: bool = False,
 | 
			
		||||
    hide_tb: bool = True,
 | 
			
		||||
    debug_kbis: bool = False,
 | 
			
		||||
    task_status: TaskStatus[
 | 
			
		||||
        Context | BaseException
 | 
			
		||||
| 
						 | 
				
			
			@ -380,9 +380,9 @@ async def _errors_relayed_via_ipc(
 | 
			
		|||
    # they can be individually ccancelled.
 | 
			
		||||
    finally:
 | 
			
		||||
 | 
			
		||||
        # if the error is not from user code and instead a failure
 | 
			
		||||
        # of a runtime RPC or transport failure we do prolly want to
 | 
			
		||||
        # show this frame
 | 
			
		||||
        # if the error is not from user code and instead a failure of
 | 
			
		||||
        # an internal-runtime-RPC or IPC-connection, we do (prolly) want
 | 
			
		||||
        # to show this frame!
 | 
			
		||||
        if (
 | 
			
		||||
            rpc_err
 | 
			
		||||
            and (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1698,10 +1698,10 @@ async def async_main(
 | 
			
		|||
        )
 | 
			
		||||
 | 
			
		||||
    op_nested_actor_repr: str = _pformat.nest_from_op(
 | 
			
		||||
        input_op=')> ',
 | 
			
		||||
        tree_str=actor.pformat(),
 | 
			
		||||
        input_op=')>',
 | 
			
		||||
        text=actor.pformat(),
 | 
			
		||||
        nest_prefix='|_',
 | 
			
		||||
        back_from_op=2,
 | 
			
		||||
        nest_indent=1,  # under >
 | 
			
		||||
    )
 | 
			
		||||
    teardown_report += (
 | 
			
		||||
        'Actor runtime exited\n'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -593,9 +593,10 @@ async def _open_and_supervise_one_cancels_all_nursery(
 | 
			
		|||
    # final exit
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@acm
 | 
			
		||||
# @api_frame
 | 
			
		||||
@acm
 | 
			
		||||
async def open_nursery(
 | 
			
		||||
    *,  # named params only!
 | 
			
		||||
    hide_tb: bool = True,
 | 
			
		||||
    **kwargs,
 | 
			
		||||
    # ^TODO, paramspec for `open_root_actor()`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -249,14 +249,29 @@ def pformat_cs(
 | 
			
		|||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# TODO: move this func to some kinda `.devx.pformat.py` eventually
 | 
			
		||||
# as we work out our multi-domain state-flow-syntax!
 | 
			
		||||
def nest_from_op(
 | 
			
		||||
    input_op: str,
 | 
			
		||||
    input_op: str,  # TODO, Literal of all op-"symbols" from below?
 | 
			
		||||
    text: str,  # TODO? better name, like `text`?
 | 
			
		||||
 | 
			
		||||
    nest_prefix: str = '|_',
 | 
			
		||||
    nest_indent: int = 0,
 | 
			
		||||
    # XXX indent `next_prefix` "to-the-right-of" `input_op`
 | 
			
		||||
    # by this count of whitespaces (' ').
 | 
			
		||||
 | 
			
		||||
) -> str:
 | 
			
		||||
    '''
 | 
			
		||||
    Depth-increment the input (presumably hierarchy/supervision)
 | 
			
		||||
    input "tree string" below the provided `input_op` execution
 | 
			
		||||
    operator, so injecting a `"\n|_{input_op}\n"`and indenting the
 | 
			
		||||
    `tree_str` to nest content aligned with the ops last char.
 | 
			
		||||
 | 
			
		||||
    '''
 | 
			
		||||
    # `sclang` "structurred-concurrency-language": an ascii-encoded
 | 
			
		||||
    # symbolic alphabet to describe concurrent systems.
 | 
			
		||||
    #
 | 
			
		||||
    # ?TODO? an idea for a syntax to the state of concurrent systems
 | 
			
		||||
    # as a "3-domain" (execution, scope, storage) model and using
 | 
			
		||||
    # a minimal ascii/utf-8 operator-set.
 | 
			
		||||
    # ?TODO? aa more fomal idea for a syntax to the state of
 | 
			
		||||
    # concurrent systems as a "3-domain" (execution, scope, storage)
 | 
			
		||||
    # model and using a minimal ascii/utf-8 operator-set.
 | 
			
		||||
    #
 | 
			
		||||
    # try not to take any of this seriously yet XD
 | 
			
		||||
    #
 | 
			
		||||
| 
						 | 
				
			
			@ -322,37 +337,29 @@ def nest_from_op(
 | 
			
		|||
    #
 | 
			
		||||
    # =>{  recv-req to open
 | 
			
		||||
    # <={  send-status that it closed
 | 
			
		||||
    #
 | 
			
		||||
    if (
 | 
			
		||||
        nest_prefix
 | 
			
		||||
        and
 | 
			
		||||
        nest_indent
 | 
			
		||||
    ):
 | 
			
		||||
        nest_prefix: str = textwrap.indent(
 | 
			
		||||
            nest_prefix,
 | 
			
		||||
            prefix=nest_indent*' ',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    tree_str: str,
 | 
			
		||||
 | 
			
		||||
    # NOTE: so move back-from-the-left of the `input_op` by
 | 
			
		||||
    # this amount.
 | 
			
		||||
    back_from_op: int = 0,
 | 
			
		||||
    nest_prefix: str = ''
 | 
			
		||||
 | 
			
		||||
) -> str:
 | 
			
		||||
    '''
 | 
			
		||||
    Depth-increment the input (presumably hierarchy/supervision)
 | 
			
		||||
    input "tree string" below the provided `input_op` execution
 | 
			
		||||
    operator, so injecting a `"\n|_{input_op}\n"`and indenting the
 | 
			
		||||
    `tree_str` to nest content aligned with the ops last char.
 | 
			
		||||
 | 
			
		||||
    '''
 | 
			
		||||
    tree_str_indent: int = len(nest_prefix)
 | 
			
		||||
    indented_tree_str: str = textwrap.indent(
 | 
			
		||||
        tree_str,
 | 
			
		||||
        prefix=' ' *(
 | 
			
		||||
            len(input_op)
 | 
			
		||||
            -
 | 
			
		||||
            (back_from_op + 1)
 | 
			
		||||
        ),
 | 
			
		||||
        text,
 | 
			
		||||
        prefix=' '*tree_str_indent
 | 
			
		||||
    )
 | 
			
		||||
    # inject any provided nesting-prefix chars
 | 
			
		||||
    # into the head of the first line.
 | 
			
		||||
    if nest_prefix:
 | 
			
		||||
        indented_tree_str: str = (
 | 
			
		||||
            f'{nest_prefix}'
 | 
			
		||||
            f'{indented_tree_str[len(nest_prefix):]}'
 | 
			
		||||
            f'{nest_prefix}{indented_tree_str[tree_str_indent:]}'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        f'{input_op}\n'
 | 
			
		||||
        f'{indented_tree_str}'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue