Compare commits
	
		
			5 Commits 
		
	
	
		
			e696caf810
			...
			e536057fea
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								 | 
						e536057fea | |
| 
							
							
								 | 
						c6b4da5788 | |
| 
							
							
								 | 
						1f7f84fdfa | |
| 
							
							
								 | 
						a5bdc6db66 | |
| 
							
							
								 | 
						9a18b57d38 | 
| 
						 | 
					@ -329,7 +329,7 @@ async def inf_streamer(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # close out the stream gracefully
 | 
					            # close out the stream gracefully
 | 
				
			||||||
            except trio.ClosedResourceError:
 | 
					            except trio.ClosedResourceError:
 | 
				
			||||||
                print('msgstream closed on streamer side!')
 | 
					                print('transport closed on streamer side!')
 | 
				
			||||||
                assert stream.closed
 | 
					                assert stream.closed
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,6 @@ from contextlib import asynccontextmanager as acm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
import trio
 | 
					import trio
 | 
				
			||||||
from trio_typing import TaskStatus
 | 
					 | 
				
			||||||
import tractor
 | 
					import tractor
 | 
				
			||||||
from tractor import RemoteActorError
 | 
					from tractor import RemoteActorError
 | 
				
			||||||
from async_generator import aclosing
 | 
					from async_generator import aclosing
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,12 +10,13 @@ TODO:
 | 
				
			||||||
    - wonder if any of it'll work on OS X?
 | 
					    - wonder if any of it'll work on OS X?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					from functools import partial
 | 
				
			||||||
import itertools
 | 
					import itertools
 | 
				
			||||||
from os import path
 | 
					# from os import path
 | 
				
			||||||
from typing import Optional
 | 
					from typing import Optional
 | 
				
			||||||
import platform
 | 
					import platform
 | 
				
			||||||
import pathlib
 | 
					import pathlib
 | 
				
			||||||
import sys
 | 
					# import sys
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
| 
						 | 
					@ -25,6 +26,10 @@ from pexpect.exceptions import (
 | 
				
			||||||
    EOF,
 | 
					    EOF,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from tractor.devx._debug import (
 | 
				
			||||||
 | 
					    _pause_msg,
 | 
				
			||||||
 | 
					    _crash_msg,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from conftest import (
 | 
					from conftest import (
 | 
				
			||||||
    examples_dir,
 | 
					    examples_dir,
 | 
				
			||||||
    _ci_env,
 | 
					    _ci_env,
 | 
				
			||||||
| 
						 | 
					@ -123,20 +128,52 @@ def expect(
 | 
				
			||||||
        raise
 | 
					        raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def in_prompt_msg(
 | 
				
			||||||
 | 
					    prompt: str,
 | 
				
			||||||
 | 
					    parts: list[str],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pause_on_false: bool = False,
 | 
				
			||||||
 | 
					    print_prompt_on_false: bool = True,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					) -> bool:
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
 | 
					    Predicate check if (the prompt's) std-streams output has all
 | 
				
			||||||
 | 
					    `str`-parts in it.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Can be used in test asserts for bulk matching expected
 | 
				
			||||||
 | 
					    log/REPL output for a given `pdb` interact point.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
 | 
					    for part in parts:
 | 
				
			||||||
 | 
					        if part not in prompt:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if pause_on_false:
 | 
				
			||||||
 | 
					                import pdbp
 | 
				
			||||||
 | 
					                pdbp.set_trace()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if print_prompt_on_false:
 | 
				
			||||||
 | 
					                print(prompt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def assert_before(
 | 
					def assert_before(
 | 
				
			||||||
    child,
 | 
					    child,
 | 
				
			||||||
    patts: list[str],
 | 
					    patts: list[str],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    **kwargs,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    before = str(child.before.decode())
 | 
					    # as in before the prompt end
 | 
				
			||||||
 | 
					    before: str = str(child.before.decode())
 | 
				
			||||||
 | 
					    assert in_prompt_msg(
 | 
				
			||||||
 | 
					        prompt=before,
 | 
				
			||||||
 | 
					        parts=patts,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for patt in patts:
 | 
					        **kwargs
 | 
				
			||||||
        try:
 | 
					    )
 | 
				
			||||||
            assert patt in before
 | 
					 | 
				
			||||||
        except AssertionError:
 | 
					 | 
				
			||||||
            print(before)
 | 
					 | 
				
			||||||
            raise
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.fixture(
 | 
					@pytest.fixture(
 | 
				
			||||||
| 
						 | 
					@ -195,7 +232,10 @@ def test_root_actor_error(spawn, user_in_out):
 | 
				
			||||||
    before = str(child.before.decode())
 | 
					    before = str(child.before.decode())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # make sure expected logging and error arrives
 | 
					    # make sure expected logging and error arrives
 | 
				
			||||||
    assert "Attaching to pdb in crashed actor: ('root'" in before
 | 
					    assert in_prompt_msg(
 | 
				
			||||||
 | 
					        before,
 | 
				
			||||||
 | 
					        [_crash_msg, "('root'"]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    assert 'AssertionError' in before
 | 
					    assert 'AssertionError' in before
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # send user command
 | 
					    # send user command
 | 
				
			||||||
| 
						 | 
					@ -332,7 +372,10 @@ def test_subactor_error(
 | 
				
			||||||
    child.expect(PROMPT)
 | 
					    child.expect(PROMPT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    before = str(child.before.decode())
 | 
					    before = str(child.before.decode())
 | 
				
			||||||
    assert "Attaching to pdb in crashed actor: ('name_error'" in before
 | 
					    assert in_prompt_msg(
 | 
				
			||||||
 | 
					        before,
 | 
				
			||||||
 | 
					        [_crash_msg, "('name_error'"]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if do_next:
 | 
					    if do_next:
 | 
				
			||||||
        child.sendline('n')
 | 
					        child.sendline('n')
 | 
				
			||||||
| 
						 | 
					@ -353,9 +396,15 @@ def test_subactor_error(
 | 
				
			||||||
    before = str(child.before.decode())
 | 
					    before = str(child.before.decode())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # root actor gets debugger engaged
 | 
					    # root actor gets debugger engaged
 | 
				
			||||||
    assert "Attaching to pdb in crashed actor: ('root'" in before
 | 
					    assert in_prompt_msg(
 | 
				
			||||||
 | 
					        before,
 | 
				
			||||||
 | 
					        [_crash_msg, "('root'"]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    # error is a remote error propagated from the subactor
 | 
					    # error is a remote error propagated from the subactor
 | 
				
			||||||
    assert "RemoteActorError: ('name_error'" in before
 | 
					    assert in_prompt_msg(
 | 
				
			||||||
 | 
					        before,
 | 
				
			||||||
 | 
					        [_crash_msg, "('name_error'"]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # another round
 | 
					    # another round
 | 
				
			||||||
    if ctlc:
 | 
					    if ctlc:
 | 
				
			||||||
| 
						 | 
					@ -380,7 +429,10 @@ def test_subactor_breakpoint(
 | 
				
			||||||
    child.expect(PROMPT)
 | 
					    child.expect(PROMPT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    before = str(child.before.decode())
 | 
					    before = str(child.before.decode())
 | 
				
			||||||
    assert "Attaching pdb to actor: ('breakpoint_forever'" in before
 | 
					    assert in_prompt_msg(
 | 
				
			||||||
 | 
					        before,
 | 
				
			||||||
 | 
					        [_pause_msg, "('breakpoint_forever'"]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # do some "next" commands to demonstrate recurrent breakpoint
 | 
					    # do some "next" commands to demonstrate recurrent breakpoint
 | 
				
			||||||
    # entries
 | 
					    # entries
 | 
				
			||||||
| 
						 | 
					@ -396,7 +448,10 @@ def test_subactor_breakpoint(
 | 
				
			||||||
        child.sendline('continue')
 | 
					        child.sendline('continue')
 | 
				
			||||||
        child.expect(PROMPT)
 | 
					        child.expect(PROMPT)
 | 
				
			||||||
        before = str(child.before.decode())
 | 
					        before = str(child.before.decode())
 | 
				
			||||||
        assert "Attaching pdb to actor: ('breakpoint_forever'" in before
 | 
					        assert in_prompt_msg(
 | 
				
			||||||
 | 
					            before,
 | 
				
			||||||
 | 
					            [_pause_msg, "('breakpoint_forever'"]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ctlc:
 | 
					        if ctlc:
 | 
				
			||||||
            do_ctlc(child)
 | 
					            do_ctlc(child)
 | 
				
			||||||
| 
						 | 
					@ -441,7 +496,10 @@ def test_multi_subactors(
 | 
				
			||||||
    child.expect(PROMPT)
 | 
					    child.expect(PROMPT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    before = str(child.before.decode())
 | 
					    before = str(child.before.decode())
 | 
				
			||||||
    assert "Attaching pdb to actor: ('breakpoint_forever'" in before
 | 
					    assert in_prompt_msg(
 | 
				
			||||||
 | 
					        before,
 | 
				
			||||||
 | 
					        [_pause_msg, "('breakpoint_forever'"]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if ctlc:
 | 
					    if ctlc:
 | 
				
			||||||
        do_ctlc(child)
 | 
					        do_ctlc(child)
 | 
				
			||||||
| 
						 | 
					@ -461,7 +519,10 @@ def test_multi_subactors(
 | 
				
			||||||
    # first name_error failure
 | 
					    # first name_error failure
 | 
				
			||||||
    child.expect(PROMPT)
 | 
					    child.expect(PROMPT)
 | 
				
			||||||
    before = str(child.before.decode())
 | 
					    before = str(child.before.decode())
 | 
				
			||||||
    assert "Attaching to pdb in crashed actor: ('name_error'" in before
 | 
					    assert in_prompt_msg(
 | 
				
			||||||
 | 
					        before,
 | 
				
			||||||
 | 
					        [_crash_msg, "('name_error'"]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    assert "NameError" in before
 | 
					    assert "NameError" in before
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if ctlc:
 | 
					    if ctlc:
 | 
				
			||||||
| 
						 | 
					@ -487,7 +548,10 @@ def test_multi_subactors(
 | 
				
			||||||
    child.sendline('c')
 | 
					    child.sendline('c')
 | 
				
			||||||
    child.expect(PROMPT)
 | 
					    child.expect(PROMPT)
 | 
				
			||||||
    before = str(child.before.decode())
 | 
					    before = str(child.before.decode())
 | 
				
			||||||
    assert "Attaching pdb to actor: ('breakpoint_forever'" in before
 | 
					    assert in_prompt_msg(
 | 
				
			||||||
 | 
					        before,
 | 
				
			||||||
 | 
					        [_pause_msg, "('breakpoint_forever'"]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if ctlc:
 | 
					    if ctlc:
 | 
				
			||||||
        do_ctlc(child)
 | 
					        do_ctlc(child)
 | 
				
			||||||
| 
						 | 
					@ -527,17 +591,21 @@ def test_multi_subactors(
 | 
				
			||||||
    child.expect(PROMPT)
 | 
					    child.expect(PROMPT)
 | 
				
			||||||
    before = str(child.before.decode())
 | 
					    before = str(child.before.decode())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert_before(child, [
 | 
					    assert_before(
 | 
				
			||||||
        # debugger attaches to root
 | 
					        child, [
 | 
				
			||||||
        "Attaching to pdb in crashed actor: ('root'",
 | 
					            # debugger attaches to root
 | 
				
			||||||
 | 
					            # "Attaching to pdb in crashed actor: ('root'",
 | 
				
			||||||
 | 
					            _crash_msg,
 | 
				
			||||||
 | 
					            "('root'",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # expect a multierror with exceptions for each sub-actor
 | 
					            # expect a multierror with exceptions for each sub-actor
 | 
				
			||||||
        "RemoteActorError: ('breakpoint_forever'",
 | 
					            "RemoteActorError: ('breakpoint_forever'",
 | 
				
			||||||
        "RemoteActorError: ('name_error'",
 | 
					            "RemoteActorError: ('name_error'",
 | 
				
			||||||
        "RemoteActorError: ('spawn_error'",
 | 
					            "RemoteActorError: ('spawn_error'",
 | 
				
			||||||
        "RemoteActorError: ('name_error_1'",
 | 
					            "RemoteActorError: ('name_error_1'",
 | 
				
			||||||
        'bdb.BdbQuit',
 | 
					            'bdb.BdbQuit',
 | 
				
			||||||
    ])
 | 
					        ]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if ctlc:
 | 
					    if ctlc:
 | 
				
			||||||
        do_ctlc(child)
 | 
					        do_ctlc(child)
 | 
				
			||||||
| 
						 | 
					@ -574,15 +642,22 @@ def test_multi_daemon_subactors(
 | 
				
			||||||
    # the root's tty lock first so anticipate either crash
 | 
					    # the root's tty lock first so anticipate either crash
 | 
				
			||||||
    # message on the first entry.
 | 
					    # message on the first entry.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bp_forever_msg = "Attaching pdb to actor: ('bp_forever'"
 | 
					    bp_forev_parts = [_pause_msg, "('bp_forever'"]
 | 
				
			||||||
 | 
					    bp_forev_in_msg = partial(
 | 
				
			||||||
 | 
					        in_prompt_msg,
 | 
				
			||||||
 | 
					        parts=bp_forev_parts,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    name_error_msg = "NameError: name 'doggypants' is not defined"
 | 
					    name_error_msg = "NameError: name 'doggypants' is not defined"
 | 
				
			||||||
 | 
					    name_error_parts = [name_error_msg]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    before = str(child.before.decode())
 | 
					    before = str(child.before.decode())
 | 
				
			||||||
    if bp_forever_msg in before:
 | 
					
 | 
				
			||||||
        next_msg = name_error_msg
 | 
					    if bp_forev_in_msg(prompt=before):
 | 
				
			||||||
 | 
					        next_parts = name_error_parts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    elif name_error_msg in before:
 | 
					    elif name_error_msg in before:
 | 
				
			||||||
        next_msg = bp_forever_msg
 | 
					        next_parts = bp_forev_parts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        raise ValueError("Neither log msg was found !?")
 | 
					        raise ValueError("Neither log msg was found !?")
 | 
				
			||||||
| 
						 | 
					@ -599,7 +674,10 @@ def test_multi_daemon_subactors(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    child.sendline('c')
 | 
					    child.sendline('c')
 | 
				
			||||||
    child.expect(PROMPT)
 | 
					    child.expect(PROMPT)
 | 
				
			||||||
    assert_before(child, [next_msg])
 | 
					    assert_before(
 | 
				
			||||||
 | 
					        child,
 | 
				
			||||||
 | 
					        next_parts,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # XXX: hooray the root clobbering the child here was fixed!
 | 
					    # XXX: hooray the root clobbering the child here was fixed!
 | 
				
			||||||
    # IMO, this demonstrates the true power of SC system design.
 | 
					    # IMO, this demonstrates the true power of SC system design.
 | 
				
			||||||
| 
						 | 
					@ -623,9 +701,15 @@ def test_multi_daemon_subactors(
 | 
				
			||||||
    child.expect(PROMPT)
 | 
					    child.expect(PROMPT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        assert_before(child, [bp_forever_msg])
 | 
					        assert_before(
 | 
				
			||||||
 | 
					            child,
 | 
				
			||||||
 | 
					            bp_forev_parts,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    except AssertionError:
 | 
					    except AssertionError:
 | 
				
			||||||
        assert_before(child, [name_error_msg])
 | 
					        assert_before(
 | 
				
			||||||
 | 
					            child,
 | 
				
			||||||
 | 
					            name_error_parts,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        if ctlc:
 | 
					        if ctlc:
 | 
				
			||||||
| 
						 | 
					@ -637,7 +721,10 @@ def test_multi_daemon_subactors(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        child.sendline('c')
 | 
					        child.sendline('c')
 | 
				
			||||||
        child.expect(PROMPT)
 | 
					        child.expect(PROMPT)
 | 
				
			||||||
        assert_before(child, [name_error_msg])
 | 
					        assert_before(
 | 
				
			||||||
 | 
					            child,
 | 
				
			||||||
 | 
					            name_error_parts,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # wait for final error in root
 | 
					    # wait for final error in root
 | 
				
			||||||
    # where it crashs with boxed error
 | 
					    # where it crashs with boxed error
 | 
				
			||||||
| 
						 | 
					@ -647,7 +734,7 @@ def test_multi_daemon_subactors(
 | 
				
			||||||
            child.expect(PROMPT)
 | 
					            child.expect(PROMPT)
 | 
				
			||||||
            assert_before(
 | 
					            assert_before(
 | 
				
			||||||
                child,
 | 
					                child,
 | 
				
			||||||
                [bp_forever_msg]
 | 
					                bp_forev_parts
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        except AssertionError:
 | 
					        except AssertionError:
 | 
				
			||||||
            break
 | 
					            break
 | 
				
			||||||
| 
						 | 
					@ -656,7 +743,9 @@ def test_multi_daemon_subactors(
 | 
				
			||||||
        child,
 | 
					        child,
 | 
				
			||||||
        [
 | 
					        [
 | 
				
			||||||
            # boxed error raised in root task
 | 
					            # boxed error raised in root task
 | 
				
			||||||
            "Attaching to pdb in crashed actor: ('root'",
 | 
					            # "Attaching to pdb in crashed actor: ('root'",
 | 
				
			||||||
 | 
					            _crash_msg,
 | 
				
			||||||
 | 
					            "('root'",
 | 
				
			||||||
            "_exceptions.RemoteActorError: ('name_error'",
 | 
					            "_exceptions.RemoteActorError: ('name_error'",
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
| 
						 | 
					@ -770,7 +859,7 @@ def test_multi_nested_subactors_error_through_nurseries(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    child = spawn('multi_nested_subactors_error_up_through_nurseries')
 | 
					    child = spawn('multi_nested_subactors_error_up_through_nurseries')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    timed_out_early: bool = False
 | 
					    # timed_out_early: bool = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for send_char in itertools.cycle(['c', 'q']):
 | 
					    for send_char in itertools.cycle(['c', 'q']):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
| 
						 | 
					@ -871,11 +960,14 @@ def test_root_nursery_cancels_before_child_releases_tty_lock(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not timed_out_early:
 | 
					    if not timed_out_early:
 | 
				
			||||||
        before = str(child.before.decode())
 | 
					        before = str(child.before.decode())
 | 
				
			||||||
        assert_before(child, [
 | 
					        assert_before(
 | 
				
			||||||
            "tractor._exceptions.RemoteActorError: ('spawner0'",
 | 
					            child,
 | 
				
			||||||
            "tractor._exceptions.RemoteActorError: ('name_error'",
 | 
					            [
 | 
				
			||||||
            "NameError: name 'doggypants' is not defined",
 | 
					                "tractor._exceptions.RemoteActorError: ('spawner0'",
 | 
				
			||||||
        ])
 | 
					                "tractor._exceptions.RemoteActorError: ('name_error'",
 | 
				
			||||||
 | 
					                "NameError: name 'doggypants' is not defined",
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_root_cancels_child_context_during_startup(
 | 
					def test_root_cancels_child_context_during_startup(
 | 
				
			||||||
| 
						 | 
					@ -909,8 +1001,10 @@ def test_different_debug_mode_per_actor(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # only one actor should enter the debugger
 | 
					    # only one actor should enter the debugger
 | 
				
			||||||
    before = str(child.before.decode())
 | 
					    before = str(child.before.decode())
 | 
				
			||||||
    assert "Attaching to pdb in crashed actor: ('debugged_boi'" in before
 | 
					    assert in_prompt_msg(
 | 
				
			||||||
    assert "RuntimeError" in before
 | 
					        before,
 | 
				
			||||||
 | 
					        [_crash_msg, "('debugged_boi'", "RuntimeError"],
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if ctlc:
 | 
					    if ctlc:
 | 
				
			||||||
        do_ctlc(child)
 | 
					        do_ctlc(child)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -134,7 +134,7 @@ def test_rpc_errors(
 | 
				
			||||||
        value = err.value
 | 
					        value = err.value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # might get multiple `trio.Cancelled`s as well inside an inception
 | 
					        # might get multiple `trio.Cancelled`s as well inside an inception
 | 
				
			||||||
        if isinstance(value, trio.MultiError):
 | 
					        if isinstance(value, ExceptionGroup):
 | 
				
			||||||
            value = next(itertools.dropwhile(
 | 
					            value = next(itertools.dropwhile(
 | 
				
			||||||
                lambda exc: not isinstance(exc, tractor.RemoteActorError),
 | 
					                lambda exc: not isinstance(exc, tractor.RemoteActorError),
 | 
				
			||||||
                value.exceptions
 | 
					                value.exceptions
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -116,14 +116,18 @@ def _trio_main(
 | 
				
			||||||
    if actor.loglevel is not None:
 | 
					    if actor.loglevel is not None:
 | 
				
			||||||
        get_console_log(actor.loglevel)
 | 
					        get_console_log(actor.loglevel)
 | 
				
			||||||
        import os
 | 
					        import os
 | 
				
			||||||
        log.info(
 | 
					        actor_info: str = (
 | 
				
			||||||
            'Started new trio process:\n'
 | 
					 | 
				
			||||||
            f'|_{actor}\n'
 | 
					            f'|_{actor}\n'
 | 
				
			||||||
            f'  uid: {actor.uid}\n'
 | 
					            f'  uid: {actor.uid}\n'
 | 
				
			||||||
            f'  pid: {os.getpid()}\n'
 | 
					            f'  pid: {os.getpid()}\n'
 | 
				
			||||||
            f'  parent_addr: {parent_addr}\n'
 | 
					            f'  parent_addr: {parent_addr}\n'
 | 
				
			||||||
            f'  loglevel: {actor.loglevel}\n'
 | 
					            f'  loglevel: {actor.loglevel}\n'
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        log.info(
 | 
				
			||||||
 | 
					            'Started new trio process:\n'
 | 
				
			||||||
 | 
					            +
 | 
				
			||||||
 | 
					            actor_info
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        if infect_asyncio:
 | 
					        if infect_asyncio:
 | 
				
			||||||
| 
						 | 
					@ -133,8 +137,14 @@ def _trio_main(
 | 
				
			||||||
            trio.run(trio_main)
 | 
					            trio.run(trio_main)
 | 
				
			||||||
    except KeyboardInterrupt:
 | 
					    except KeyboardInterrupt:
 | 
				
			||||||
        log.cancel(
 | 
					        log.cancel(
 | 
				
			||||||
            f'@{actor.uid} received KBI'
 | 
					            'Actor received KBI\n'
 | 
				
			||||||
 | 
					            +
 | 
				
			||||||
 | 
					            actor_info
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    finally:
 | 
					    finally:
 | 
				
			||||||
        log.info(f"Actor {actor.uid} terminated")
 | 
					        log.info(
 | 
				
			||||||
 | 
					            'Actor terminated\n'
 | 
				
			||||||
 | 
					            +
 | 
				
			||||||
 | 
					            actor_info
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -257,7 +257,7 @@ class Portal:
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        reminfo: str = (
 | 
					        reminfo: str = (
 | 
				
			||||||
            f'{self.channel.uid}\n'
 | 
					            f'`Portal.cancel_actor()` => {self.channel.uid}\n'
 | 
				
			||||||
            f' |_{chan}\n'
 | 
					            f' |_{chan}\n'
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        log.cancel(
 | 
					        log.cancel(
 | 
				
			||||||
| 
						 | 
					@ -949,9 +949,13 @@ class Portal:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # CASE 1
 | 
					                # CASE 1
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
 | 
					                    outcome_str: str = ctx.repr_outcome(
 | 
				
			||||||
 | 
					                        show_error_fields=True,
 | 
				
			||||||
 | 
					                        # type_only=True,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                    log.cancel(
 | 
					                    log.cancel(
 | 
				
			||||||
                        f'Context terminated due to local scope error:\n'
 | 
					                        f'Context terminated due to local scope error:\n\n'
 | 
				
			||||||
                        f'{etype.__name__}\n'
 | 
					                        f'{ctx.chan.uid} => {outcome_str}\n'
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # FINALLY, remove the context from runtime tracking and
 | 
					            # FINALLY, remove the context from runtime tracking and
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -342,7 +342,9 @@ async def open_root_actor(
 | 
				
			||||||
                #     for an in nurseries:
 | 
					                #     for an in nurseries:
 | 
				
			||||||
                #         tempn.start_soon(an.exited.wait)
 | 
					                #         tempn.start_soon(an.exited.wait)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                logger.cancel("Shutting down root actor")
 | 
					                logger.info(
 | 
				
			||||||
 | 
					                    'Closing down root actor'
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
                await actor.cancel(None)  # self cancel
 | 
					                await actor.cancel(None)  # self cancel
 | 
				
			||||||
    finally:
 | 
					    finally:
 | 
				
			||||||
        _state._current_actor = None
 | 
					        _state._current_actor = None
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -21,30 +21,17 @@ and working with/on the actor runtime.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
from ._debug import (
 | 
					from ._debug import (
 | 
				
			||||||
    maybe_wait_for_debugger,
 | 
					    maybe_wait_for_debugger as maybe_wait_for_debugger,
 | 
				
			||||||
    acquire_debug_lock,
 | 
					    acquire_debug_lock as acquire_debug_lock,
 | 
				
			||||||
    breakpoint,
 | 
					    breakpoint as breakpoint,
 | 
				
			||||||
    pause,
 | 
					    pause as pause,
 | 
				
			||||||
    pause_from_sync,
 | 
					    pause_from_sync as pause_from_sync,
 | 
				
			||||||
    shield_sigint_handler,
 | 
					    shield_sigint_handler as shield_sigint_handler,
 | 
				
			||||||
    MultiActorPdb,
 | 
					    MultiActorPdb as MultiActorPdb,
 | 
				
			||||||
    open_crash_handler,
 | 
					    open_crash_handler as open_crash_handler,
 | 
				
			||||||
    maybe_open_crash_handler,
 | 
					    maybe_open_crash_handler as maybe_open_crash_handler,
 | 
				
			||||||
    post_mortem,
 | 
					    post_mortem as post_mortem,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from ._stackscope import (
 | 
					from ._stackscope import (
 | 
				
			||||||
    enable_stack_on_sig as enable_stack_on_sig,
 | 
					    enable_stack_on_sig as enable_stack_on_sig,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					 | 
				
			||||||
__all__ = [
 | 
					 | 
				
			||||||
    'maybe_wait_for_debugger',
 | 
					 | 
				
			||||||
    'acquire_debug_lock',
 | 
					 | 
				
			||||||
    'breakpoint',
 | 
					 | 
				
			||||||
    'pause',
 | 
					 | 
				
			||||||
    'pause_from_sync',
 | 
					 | 
				
			||||||
    'shield_sigint_handler',
 | 
					 | 
				
			||||||
    'MultiActorPdb',
 | 
					 | 
				
			||||||
    'open_crash_handler',
 | 
					 | 
				
			||||||
    'maybe_open_crash_handler',
 | 
					 | 
				
			||||||
    'post_mortem',
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,18 +21,19 @@ Multi-core debugging for da peeps!
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
from __future__ import annotations
 | 
					from __future__ import annotations
 | 
				
			||||||
import bdb
 | 
					import bdb
 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import signal
 | 
					 | 
				
			||||||
from functools import (
 | 
					 | 
				
			||||||
    partial,
 | 
					 | 
				
			||||||
    cached_property,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from contextlib import (
 | 
					from contextlib import (
 | 
				
			||||||
    asynccontextmanager as acm,
 | 
					    asynccontextmanager as acm,
 | 
				
			||||||
    contextmanager as cm,
 | 
					    contextmanager as cm,
 | 
				
			||||||
    nullcontext,
 | 
					    nullcontext,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from functools import (
 | 
				
			||||||
 | 
					    partial,
 | 
				
			||||||
 | 
					    cached_property,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import signal
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import traceback
 | 
				
			||||||
from typing import (
 | 
					from typing import (
 | 
				
			||||||
    Any,
 | 
					    Any,
 | 
				
			||||||
    Callable,
 | 
					    Callable,
 | 
				
			||||||
| 
						 | 
					@ -611,6 +612,9 @@ def shield_sigint_handler(
 | 
				
			||||||
        # https://github.com/prompt-toolkit/python-prompt-toolkit/blob/c2c6af8a0308f9e5d7c0e28cb8a02963fe0ce07a/prompt_toolkit/patch_stdout.py
 | 
					        # https://github.com/prompt-toolkit/python-prompt-toolkit/blob/c2c6af8a0308f9e5d7c0e28cb8a02963fe0ce07a/prompt_toolkit/patch_stdout.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_pause_msg: str = 'Attaching to pdb REPL in actor'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _set_trace(
 | 
					def _set_trace(
 | 
				
			||||||
    actor: tractor.Actor | None = None,
 | 
					    actor: tractor.Actor | None = None,
 | 
				
			||||||
    pdb: MultiActorPdb | None = None,
 | 
					    pdb: MultiActorPdb | None = None,
 | 
				
			||||||
| 
						 | 
					@ -632,7 +636,13 @@ def _set_trace(
 | 
				
			||||||
        ) or shield
 | 
					        ) or shield
 | 
				
			||||||
    ):
 | 
					    ):
 | 
				
			||||||
        # pdbp.set_trace()
 | 
					        # pdbp.set_trace()
 | 
				
			||||||
        log.pdb(f"\nAttaching pdb to actor: {actor.uid}\n")
 | 
					        # TODO: maybe print the actor supervion tree up to the
 | 
				
			||||||
 | 
					        # root here? Bo
 | 
				
			||||||
 | 
					        log.pdb(
 | 
				
			||||||
 | 
					            f'{_pause_msg}\n'
 | 
				
			||||||
 | 
					            '|\n'
 | 
				
			||||||
 | 
					            f'|_ {actor.uid}\n'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        # no f!#$&* idea, but when we're in async land
 | 
					        # no f!#$&* idea, but when we're in async land
 | 
				
			||||||
        # we need 2x frames up?
 | 
					        # we need 2x frames up?
 | 
				
			||||||
        frame = frame.f_back
 | 
					        frame = frame.f_back
 | 
				
			||||||
| 
						 | 
					@ -911,6 +921,11 @@ async def breakpoint(**kwargs):
 | 
				
			||||||
    await pause(**kwargs)
 | 
					    await pause(**kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_crash_msg: str = (
 | 
				
			||||||
 | 
					    'Attaching to pdb REPL in crashed actor'
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _post_mortem(
 | 
					def _post_mortem(
 | 
				
			||||||
    actor: tractor.Actor,
 | 
					    actor: tractor.Actor,
 | 
				
			||||||
    pdb: MultiActorPdb,
 | 
					    pdb: MultiActorPdb,
 | 
				
			||||||
| 
						 | 
					@ -921,15 +936,23 @@ def _post_mortem(
 | 
				
			||||||
    debugger instance.
 | 
					    debugger instance.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    log.pdb(f"\nAttaching to pdb in crashed actor: {actor.uid}\n")
 | 
					    # TODO: print the actor supervion tree up to the root
 | 
				
			||||||
 | 
					    # here! Bo
 | 
				
			||||||
 | 
					    log.pdb(
 | 
				
			||||||
 | 
					        f'{_crash_msg}\n'
 | 
				
			||||||
 | 
					        '|\n'
 | 
				
			||||||
 | 
					        f'|_ {actor.uid}\n'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # TODO: you need ``pdbpp`` master (at least this commit
 | 
					    # TODO: only replacing this to add the
 | 
				
			||||||
    # https://github.com/pdbpp/pdbpp/commit/b757794857f98d53e3ebbe70879663d7d843a6c2)
 | 
					    # `end=''` to the print XD
 | 
				
			||||||
    # to fix this and avoid the hang it causes. See issue:
 | 
					    # pdbp.xpm(Pdb=lambda: pdb)
 | 
				
			||||||
    # https://github.com/pdbpp/pdbpp/issues/480
 | 
					    info = sys.exc_info()
 | 
				
			||||||
    # TODO: help with a 3.10+ major release if/when it arrives.
 | 
					    print(traceback.format_exc(), end='')
 | 
				
			||||||
 | 
					    pdbp.post_mortem(
 | 
				
			||||||
    pdbp.xpm(Pdb=lambda: pdb)
 | 
					        t=info[2],
 | 
				
			||||||
 | 
					        Pdb=lambda: pdb,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
post_mortem = partial(
 | 
					post_mortem = partial(
 | 
				
			||||||
| 
						 | 
					@ -1001,13 +1024,13 @@ async def maybe_wait_for_debugger(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    header_msg: str = '',
 | 
					    header_msg: str = '',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
) -> None:
 | 
					) -> bool:  # was locked and we polled?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
        not debug_mode()
 | 
					        not debug_mode()
 | 
				
			||||||
        and not child_in_debug
 | 
					        and not child_in_debug
 | 
				
			||||||
    ):
 | 
					    ):
 | 
				
			||||||
        return
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    msg: str = header_msg
 | 
					    msg: str = header_msg
 | 
				
			||||||
| 
						 | 
					@ -1025,8 +1048,7 @@ async def maybe_wait_for_debugger(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if sub_in_debug := Lock.global_actor_in_debug:
 | 
					        if sub_in_debug := Lock.global_actor_in_debug:
 | 
				
			||||||
            msg += (
 | 
					            msg += (
 | 
				
			||||||
                'Debug `Lock` in use by subactor\n'
 | 
					                f'Debug `Lock` in use by subactor: {sub_in_debug}\n'
 | 
				
			||||||
                f'|_{sub_in_debug}\n'
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            # TODO: could this make things more deterministic?
 | 
					            # TODO: could this make things more deterministic?
 | 
				
			||||||
            # wait to see if a sub-actor task will be
 | 
					            # wait to see if a sub-actor task will be
 | 
				
			||||||
| 
						 | 
					@ -1035,12 +1057,12 @@ async def maybe_wait_for_debugger(
 | 
				
			||||||
            # XXX => but it doesn't seem to work..
 | 
					            # XXX => but it doesn't seem to work..
 | 
				
			||||||
            # await trio.testing.wait_all_tasks_blocked(cushion=0)
 | 
					            # await trio.testing.wait_all_tasks_blocked(cushion=0)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            log.pdb(
 | 
					            log.debug(
 | 
				
			||||||
                msg
 | 
					                msg
 | 
				
			||||||
                +
 | 
					                +
 | 
				
			||||||
                'Root immediately acquired debug TTY LOCK'
 | 
					                'Root immediately acquired debug TTY LOCK'
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            return
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for istep in range(poll_steps):
 | 
					        for istep in range(poll_steps):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1090,12 +1112,13 @@ async def maybe_wait_for_debugger(
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # fallthrough on failure to acquire..
 | 
					        # fallthrough on failure to acquire..
 | 
				
			||||||
        else:
 | 
					        # else:
 | 
				
			||||||
            raise RuntimeError(
 | 
					        #     raise RuntimeError(
 | 
				
			||||||
                msg
 | 
					        #         msg
 | 
				
			||||||
                +
 | 
					        #         +
 | 
				
			||||||
                'Root actor failed to acquire debug lock?'
 | 
					        #         'Root actor failed to acquire debug lock?'
 | 
				
			||||||
            )
 | 
					        #     )
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # else:
 | 
					    # else:
 | 
				
			||||||
    #     # TODO: non-root call for #320?
 | 
					    #     # TODO: non-root call for #320?
 | 
				
			||||||
| 
						 | 
					@ -1104,6 +1127,7 @@ async def maybe_wait_for_debugger(
 | 
				
			||||||
    #         subactor_uid=this_uid,
 | 
					    #         subactor_uid=this_uid,
 | 
				
			||||||
    #     ):
 | 
					    #     ):
 | 
				
			||||||
    #         pass
 | 
					    #         pass
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TODO: better naming and what additionals?
 | 
					# TODO: better naming and what additionals?
 | 
				
			||||||
# - [ ] optional runtime plugging?
 | 
					# - [ ] optional runtime plugging?
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,10 +23,6 @@ Currently popular frameworks supported are:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
from __future__ import annotations
 | 
					from __future__ import annotations
 | 
				
			||||||
from contextlib import (
 | 
					 | 
				
			||||||
    # asynccontextmanager as acm,
 | 
					 | 
				
			||||||
    contextmanager as cm,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from typing import (
 | 
					from typing import (
 | 
				
			||||||
    Any,
 | 
					    Any,
 | 
				
			||||||
    Callable,
 | 
					    Callable,
 | 
				
			||||||
| 
						 | 
					@ -36,9 +32,6 @@ from typing_extensions import Annotated
 | 
				
			||||||
import typer
 | 
					import typer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ._debug import open_crash_handler
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_runtime_vars: dict[str, Any] = {}
 | 
					_runtime_vars: dict[str, Any] = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue