Compare commits
8 Commits
3538ccd799
...
e4ec6b7b0c
Author | SHA1 | Date |
---|---|---|
Tyler Goodlet | e4ec6b7b0c | |
Tyler Goodlet | 9ce958cb4a | |
Tyler Goodlet | ce4d64ed2f | |
Tyler Goodlet | c6f599b1be | |
Tyler Goodlet | 9eb74560ad | |
Tyler Goodlet | 702dfe47d5 | |
Tyler Goodlet | d15e73557a | |
Tyler Goodlet | 74d4b5280a |
|
@ -146,9 +146,10 @@ def in_prompt_msg(
|
||||||
log/REPL output for a given `pdb` interact point.
|
log/REPL output for a given `pdb` interact point.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
__tracebackhide__: bool = False
|
||||||
|
|
||||||
for part in parts:
|
for part in parts:
|
||||||
if part not in prompt:
|
if part not in prompt:
|
||||||
|
|
||||||
if pause_on_false:
|
if pause_on_false:
|
||||||
import pdbp
|
import pdbp
|
||||||
pdbp.set_trace()
|
pdbp.set_trace()
|
||||||
|
@ -167,6 +168,7 @@ def assert_before(
|
||||||
**kwargs,
|
**kwargs,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
__tracebackhide__: bool = False
|
||||||
|
|
||||||
# as in before the prompt end
|
# as in before the prompt end
|
||||||
before: str = str(child.before.decode())
|
before: str = str(child.before.decode())
|
||||||
|
@ -219,7 +221,10 @@ def ctlc(
|
||||||
],
|
],
|
||||||
ids=lambda item: f'{item[0]} -> {item[1]}',
|
ids=lambda item: f'{item[0]} -> {item[1]}',
|
||||||
)
|
)
|
||||||
def test_root_actor_error(spawn, user_in_out):
|
def test_root_actor_error(
|
||||||
|
spawn,
|
||||||
|
user_in_out,
|
||||||
|
):
|
||||||
'''
|
'''
|
||||||
Demonstrate crash handler entering pdb from basic error in root actor.
|
Demonstrate crash handler entering pdb from basic error in root actor.
|
||||||
|
|
||||||
|
@ -465,8 +470,12 @@ def test_subactor_breakpoint(
|
||||||
child.expect(PROMPT)
|
child.expect(PROMPT)
|
||||||
|
|
||||||
before = str(child.before.decode())
|
before = str(child.before.decode())
|
||||||
assert "RemoteActorError: ('breakpoint_forever'" in before
|
assert in_prompt_msg(
|
||||||
assert 'bdb.BdbQuit' in before
|
before,
|
||||||
|
['RemoteActorError:',
|
||||||
|
"('breakpoint_forever'",
|
||||||
|
'bdb.BdbQuit',]
|
||||||
|
)
|
||||||
|
|
||||||
if ctlc:
|
if ctlc:
|
||||||
do_ctlc(child)
|
do_ctlc(child)
|
||||||
|
@ -478,8 +487,12 @@ def test_subactor_breakpoint(
|
||||||
child.expect(pexpect.EOF)
|
child.expect(pexpect.EOF)
|
||||||
|
|
||||||
before = str(child.before.decode())
|
before = str(child.before.decode())
|
||||||
assert "RemoteActorError: ('breakpoint_forever'" in before
|
assert in_prompt_msg(
|
||||||
assert 'bdb.BdbQuit' in before
|
before,
|
||||||
|
['RemoteActorError:',
|
||||||
|
"('breakpoint_forever'",
|
||||||
|
'bdb.BdbQuit',]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@has_nested_actors
|
@has_nested_actors
|
||||||
|
@ -747,8 +760,9 @@ def test_multi_daemon_subactors(
|
||||||
# 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,
|
_crash_msg,
|
||||||
"('root'",
|
"('root'", # should attach in root
|
||||||
"_exceptions.RemoteActorError: ('name_error'",
|
"_exceptions.RemoteActorError:", # with an embedded RAE for..
|
||||||
|
"('name_error'", # the src subactor which raised
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -849,10 +863,11 @@ def test_multi_nested_subactors_error_through_nurseries(
|
||||||
# https://github.com/goodboy/tractor/issues/320
|
# https://github.com/goodboy/tractor/issues/320
|
||||||
# ctlc: bool,
|
# ctlc: bool,
|
||||||
):
|
):
|
||||||
"""Verify deeply nested actors that error trigger debugger entries
|
'''
|
||||||
|
Verify deeply nested actors that error trigger debugger entries
|
||||||
at each actor nurserly (level) all the way up the tree.
|
at each actor nurserly (level) all the way up the tree.
|
||||||
|
|
||||||
"""
|
'''
|
||||||
# NOTE: previously, inside this script was a bug where if the
|
# NOTE: previously, inside this script was a bug where if the
|
||||||
# parent errors before a 2-levels-lower actor has released the lock,
|
# parent errors before a 2-levels-lower actor has released the lock,
|
||||||
# the parent tries to cancel it but it's stuck in the debugger?
|
# the parent tries to cancel it but it's stuck in the debugger?
|
||||||
|
@ -872,22 +887,31 @@ def test_multi_nested_subactors_error_through_nurseries(
|
||||||
except EOF:
|
except EOF:
|
||||||
break
|
break
|
||||||
|
|
||||||
assert_before(child, [
|
assert_before(
|
||||||
|
child,
|
||||||
|
[ # boxed source errors
|
||||||
|
"NameError: name 'doggypants' is not defined",
|
||||||
|
"tractor._exceptions.RemoteActorError:",
|
||||||
|
"('name_error'",
|
||||||
|
"bdb.BdbQuit",
|
||||||
|
|
||||||
# boxed source errors
|
# first level subtrees
|
||||||
"NameError: name 'doggypants' is not defined",
|
# "tractor._exceptions.RemoteActorError: ('spawner0'",
|
||||||
"tractor._exceptions.RemoteActorError: ('name_error'",
|
"src_uid=('spawner0'",
|
||||||
"bdb.BdbQuit",
|
|
||||||
|
|
||||||
# first level subtrees
|
# "tractor._exceptions.RemoteActorError: ('spawner1'",
|
||||||
"tractor._exceptions.RemoteActorError: ('spawner0'",
|
|
||||||
# "tractor._exceptions.RemoteActorError: ('spawner1'",
|
|
||||||
|
|
||||||
# propagation of errors up through nested subtrees
|
# propagation of errors up through nested subtrees
|
||||||
"tractor._exceptions.RemoteActorError: ('spawn_until_0'",
|
# "tractor._exceptions.RemoteActorError: ('spawn_until_0'",
|
||||||
"tractor._exceptions.RemoteActorError: ('spawn_until_1'",
|
# "tractor._exceptions.RemoteActorError: ('spawn_until_1'",
|
||||||
"tractor._exceptions.RemoteActorError: ('spawn_until_2'",
|
# "tractor._exceptions.RemoteActorError: ('spawn_until_2'",
|
||||||
])
|
# ^-NOTE-^ old RAE repr, new one is below with a field
|
||||||
|
# showing the src actor's uid.
|
||||||
|
"src_uid=('spawn_until_0'",
|
||||||
|
"relay_uid=('spawn_until_1'",
|
||||||
|
"src_uid=('spawn_until_2'",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.timeout(15)
|
@pytest.mark.timeout(15)
|
||||||
|
@ -1021,13 +1045,16 @@ def test_different_debug_mode_per_actor(
|
||||||
# msg reported back from the debug mode actor is processed.
|
# msg reported back from the debug mode actor is processed.
|
||||||
# assert "tractor._exceptions.RemoteActorError: ('debugged_boi'" in before
|
# assert "tractor._exceptions.RemoteActorError: ('debugged_boi'" in before
|
||||||
|
|
||||||
assert "tractor._exceptions.RemoteActorError: ('crash_boi'" in before
|
|
||||||
|
|
||||||
# the crash boi should not have made a debugger request but
|
# the crash boi should not have made a debugger request but
|
||||||
# instead crashed completely
|
# instead crashed completely
|
||||||
assert "tractor._exceptions.RemoteActorError: ('crash_boi'" in before
|
assert_before(
|
||||||
assert "RuntimeError" in before
|
child,
|
||||||
|
[
|
||||||
|
"tractor._exceptions.RemoteActorError:",
|
||||||
|
"src_uid=('crash_boi'",
|
||||||
|
"RuntimeError",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_pause_from_sync(
|
def test_pause_from_sync(
|
||||||
|
@ -1046,13 +1073,15 @@ def test_pause_from_sync(
|
||||||
assert_before(
|
assert_before(
|
||||||
child,
|
child,
|
||||||
[
|
[
|
||||||
'`greenback` portal opened!',
|
|
||||||
# pre-prompt line
|
# pre-prompt line
|
||||||
_pause_msg, "('root'",
|
_pause_msg,
|
||||||
|
"<Task '__main__.main'",
|
||||||
|
"('root'",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
if ctlc:
|
if ctlc:
|
||||||
do_ctlc(child)
|
do_ctlc(child)
|
||||||
|
|
||||||
child.sendline('c')
|
child.sendline('c')
|
||||||
child.expect(PROMPT)
|
child.expect(PROMPT)
|
||||||
|
|
||||||
|
@ -1069,6 +1098,7 @@ def test_pause_from_sync(
|
||||||
|
|
||||||
if ctlc:
|
if ctlc:
|
||||||
do_ctlc(child)
|
do_ctlc(child)
|
||||||
|
|
||||||
child.sendline('c')
|
child.sendline('c')
|
||||||
child.expect(PROMPT)
|
child.expect(PROMPT)
|
||||||
assert_before(
|
assert_before(
|
||||||
|
@ -1078,6 +1108,7 @@ def test_pause_from_sync(
|
||||||
|
|
||||||
if ctlc:
|
if ctlc:
|
||||||
do_ctlc(child)
|
do_ctlc(child)
|
||||||
|
|
||||||
child.sendline('c')
|
child.sendline('c')
|
||||||
child.expect(PROMPT)
|
child.expect(PROMPT)
|
||||||
# non-main thread case
|
# non-main thread case
|
||||||
|
@ -1089,5 +1120,22 @@ def test_pause_from_sync(
|
||||||
|
|
||||||
if ctlc:
|
if ctlc:
|
||||||
do_ctlc(child)
|
do_ctlc(child)
|
||||||
|
|
||||||
child.sendline('c')
|
child.sendline('c')
|
||||||
child.expect(pexpect.EOF)
|
child.expect(pexpect.EOF)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO!
|
||||||
|
def test_correct_frames_below_hidden():
|
||||||
|
'''
|
||||||
|
Ensure that once a `tractor.pause()` enages, when the user
|
||||||
|
inputs a "next"/"n" command the actual next line steps
|
||||||
|
and that using a "step"/"s" into the next LOC, particuarly
|
||||||
|
`tractor` APIs, you can step down into that code.
|
||||||
|
|
||||||
|
'''
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def test_cant_pause_from_paused_task():
|
||||||
|
...
|
||||||
|
|
|
@ -33,6 +33,7 @@ from .log import (
|
||||||
get_logger,
|
get_logger,
|
||||||
)
|
)
|
||||||
from . import _state
|
from . import _state
|
||||||
|
from .devx import _debug
|
||||||
from .to_asyncio import run_as_asyncio_guest
|
from .to_asyncio import run_as_asyncio_guest
|
||||||
from ._runtime import (
|
from ._runtime import (
|
||||||
async_main,
|
async_main,
|
||||||
|
@ -96,7 +97,6 @@ def _mp_main(
|
||||||
|
|
||||||
|
|
||||||
def _trio_main(
|
def _trio_main(
|
||||||
|
|
||||||
actor: Actor,
|
actor: Actor,
|
||||||
*,
|
*,
|
||||||
parent_addr: tuple[str, int] | None = None,
|
parent_addr: tuple[str, int] | None = None,
|
||||||
|
@ -107,7 +107,9 @@ def _trio_main(
|
||||||
Entry point for a `trio_run_in_process` subactor.
|
Entry point for a `trio_run_in_process` subactor.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
__tracebackhide__: bool = True
|
# __tracebackhide__: bool = True
|
||||||
|
_debug.hide_runtime_frames()
|
||||||
|
|
||||||
_state._current_actor = actor
|
_state._current_actor = actor
|
||||||
trio_main = partial(
|
trio_main = partial(
|
||||||
async_main,
|
async_main,
|
||||||
|
@ -146,7 +148,6 @@ def _trio_main(
|
||||||
+
|
+
|
||||||
actor_info
|
actor_info
|
||||||
)
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
log.info(
|
log.info(
|
||||||
'Subactor terminated\n'
|
'Subactor terminated\n'
|
||||||
|
|
|
@ -35,7 +35,6 @@ import trio
|
||||||
from msgspec import (
|
from msgspec import (
|
||||||
defstruct,
|
defstruct,
|
||||||
msgpack,
|
msgpack,
|
||||||
Raw,
|
|
||||||
structs,
|
structs,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
)
|
)
|
||||||
|
@ -44,11 +43,12 @@ from tractor._state import current_actor
|
||||||
from tractor.log import get_logger
|
from tractor.log import get_logger
|
||||||
from tractor.msg import (
|
from tractor.msg import (
|
||||||
Error,
|
Error,
|
||||||
|
PayloadMsg,
|
||||||
MsgType,
|
MsgType,
|
||||||
Stop,
|
|
||||||
types as msgtypes,
|
|
||||||
MsgCodec,
|
MsgCodec,
|
||||||
MsgDec,
|
MsgDec,
|
||||||
|
Stop,
|
||||||
|
types as msgtypes,
|
||||||
)
|
)
|
||||||
from tractor.msg.pretty_struct import (
|
from tractor.msg.pretty_struct import (
|
||||||
iter_fields,
|
iter_fields,
|
||||||
|
@ -156,6 +156,7 @@ def pack_from_raise(
|
||||||
`Error`-msg using `pack_error()` to extract the tb info.
|
`Error`-msg using `pack_error()` to extract the tb info.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
__tracebackhide__: bool = True
|
||||||
try:
|
try:
|
||||||
raise local_err
|
raise local_err
|
||||||
except type(local_err) as local_err:
|
except type(local_err) as local_err:
|
||||||
|
@ -525,10 +526,26 @@ class RemoteActorError(Exception):
|
||||||
if not with_type_header:
|
if not with_type_header:
|
||||||
body = '\n' + body
|
body = '\n' + body
|
||||||
else:
|
else:
|
||||||
body: str = textwrap.indent(
|
first: str = ''
|
||||||
self._message,
|
message: str = self._message
|
||||||
prefix=' ',
|
|
||||||
) + '\n'
|
# split off the first line so it isn't indented
|
||||||
|
# the same like the "boxed content".
|
||||||
|
if not with_type_header:
|
||||||
|
lines: list[str] = message.splitlines()
|
||||||
|
first = lines[0]
|
||||||
|
message = ''.join(lines[1:])
|
||||||
|
|
||||||
|
body: str = (
|
||||||
|
first
|
||||||
|
+
|
||||||
|
textwrap.indent(
|
||||||
|
message,
|
||||||
|
prefix=' ',
|
||||||
|
)
|
||||||
|
+
|
||||||
|
'\n'
|
||||||
|
)
|
||||||
|
|
||||||
if with_type_header:
|
if with_type_header:
|
||||||
tail: str = ')>'
|
tail: str = ')>'
|
||||||
|
@ -734,25 +751,38 @@ class MsgTypeError(
|
||||||
def from_decode(
|
def from_decode(
|
||||||
cls,
|
cls,
|
||||||
message: str,
|
message: str,
|
||||||
msgdict: dict,
|
|
||||||
|
ipc_msg: PayloadMsg|None = None,
|
||||||
|
msgdict: dict|None = None,
|
||||||
|
|
||||||
) -> MsgTypeError:
|
) -> MsgTypeError:
|
||||||
return cls(
|
'''
|
||||||
message=message,
|
Constuctor for easy creation from (presumably) catching
|
||||||
boxed_type=cls,
|
the backend interchange lib's underlying validation error
|
||||||
|
and passing context-specific meta-data to `_mk_msg_type_err()`
|
||||||
|
(which is normally the caller of this).
|
||||||
|
|
||||||
# NOTE: original "vanilla decode" of the msg-bytes
|
'''
|
||||||
# is placed inside a value readable from
|
# if provided, expand and pack all RAE compat fields into the
|
||||||
# `.msgdata['_msg_dict']`
|
# `._extra_msgdata` auxillary data `dict` internal to
|
||||||
_msg_dict=msgdict,
|
# `RemoteActorError`.
|
||||||
|
extra_msgdata: dict = {}
|
||||||
# expand and pack all RAE compat fields
|
if msgdict:
|
||||||
# into the `._extra_msgdata` aux `dict`.
|
extra_msgdata: dict = {
|
||||||
**{
|
|
||||||
k: v
|
k: v
|
||||||
for k, v in msgdict.items()
|
for k, v in msgdict.items()
|
||||||
if k in _ipcmsg_keys
|
if k in _ipcmsg_keys
|
||||||
},
|
}
|
||||||
|
# NOTE: original "vanilla decode" of the msg-bytes
|
||||||
|
# is placed inside a value readable from
|
||||||
|
# `.msgdata['_msg_dict']`
|
||||||
|
extra_msgdata['_msg_dict'] = msgdict
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
message=message,
|
||||||
|
boxed_type=cls,
|
||||||
|
ipc_msg=ipc_msg,
|
||||||
|
**extra_msgdata,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1076,7 +1106,7 @@ _raise_from_no_key_in_msg = _raise_from_unexpected_msg
|
||||||
|
|
||||||
|
|
||||||
def _mk_msg_type_err(
|
def _mk_msg_type_err(
|
||||||
msg: Any|bytes|Raw,
|
msg: Any|bytes|MsgType,
|
||||||
codec: MsgCodec|MsgDec,
|
codec: MsgCodec|MsgDec,
|
||||||
|
|
||||||
message: str|None = None,
|
message: str|None = None,
|
||||||
|
@ -1085,6 +1115,7 @@ def _mk_msg_type_err(
|
||||||
src_validation_error: ValidationError|None = None,
|
src_validation_error: ValidationError|None = None,
|
||||||
src_type_error: TypeError|None = None,
|
src_type_error: TypeError|None = None,
|
||||||
is_invalid_payload: bool = False,
|
is_invalid_payload: bool = False,
|
||||||
|
src_err_msg: Error|None = None,
|
||||||
|
|
||||||
**mte_kwargs,
|
**mte_kwargs,
|
||||||
|
|
||||||
|
@ -1159,9 +1190,10 @@ def _mk_msg_type_err(
|
||||||
# only the payload being wrong?
|
# only the payload being wrong?
|
||||||
# -[ ] maybe the better design is to break this construct
|
# -[ ] maybe the better design is to break this construct
|
||||||
# logic into a separate explicit helper raiser-func?
|
# logic into a separate explicit helper raiser-func?
|
||||||
msg_dict: dict = {}
|
msg_dict = None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
msg: bytes
|
||||||
# decode the msg-bytes using the std msgpack
|
# decode the msg-bytes using the std msgpack
|
||||||
# interchange-prot (i.e. without any
|
# interchange-prot (i.e. without any
|
||||||
# `msgspec.Struct` handling) so that we can
|
# `msgspec.Struct` handling) so that we can
|
||||||
|
@ -1206,6 +1238,14 @@ def _mk_msg_type_err(
|
||||||
msgtyperr = MsgTypeError.from_decode(
|
msgtyperr = MsgTypeError.from_decode(
|
||||||
message=message,
|
message=message,
|
||||||
msgdict=msg_dict,
|
msgdict=msg_dict,
|
||||||
|
|
||||||
|
# NOTE: for the send-side `.started()` pld-validate
|
||||||
|
# case we actually set the `._ipc_msg` AFTER we return
|
||||||
|
# from here inside `Context.started()` since we actually
|
||||||
|
# want to emulate the `Error` from the mte we build here
|
||||||
|
# Bo
|
||||||
|
# so by default in that case this is set to `None`
|
||||||
|
ipc_msg=src_err_msg,
|
||||||
)
|
)
|
||||||
msgtyperr.__cause__ = src_validation_error
|
msgtyperr.__cause__ = src_validation_error
|
||||||
return msgtyperr
|
return msgtyperr
|
||||||
|
|
|
@ -91,12 +91,16 @@ async def open_root_actor(
|
||||||
# and that this call creates it.
|
# and that this call creates it.
|
||||||
ensure_registry: bool = False,
|
ensure_registry: bool = False,
|
||||||
|
|
||||||
|
hide_tb: bool = True,
|
||||||
|
|
||||||
) -> Actor:
|
) -> Actor:
|
||||||
'''
|
'''
|
||||||
Runtime init entry point for ``tractor``.
|
Runtime init entry point for ``tractor``.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
__tracebackhide__ = True
|
__tracebackhide__: bool = hide_tb
|
||||||
|
_debug.hide_runtime_frames()
|
||||||
|
|
||||||
# TODO: stick this in a `@cm` defined in `devx._debug`?
|
# TODO: stick this in a `@cm` defined in `devx._debug`?
|
||||||
#
|
#
|
||||||
# Override the global debugger hook to make it play nice with
|
# Override the global debugger hook to make it play nice with
|
||||||
|
@ -125,7 +129,7 @@ async def open_root_actor(
|
||||||
# usage by a clobbered TTY's stdstreams!
|
# usage by a clobbered TTY's stdstreams!
|
||||||
def block_bps(*args, **kwargs):
|
def block_bps(*args, **kwargs):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
'Trying to use `breakpoint()` eh?\n'
|
'Trying to use `breakpoint()` eh?\n\n'
|
||||||
'Welp, `tractor` blocks `breakpoint()` built-in calls by default!\n'
|
'Welp, `tractor` blocks `breakpoint()` built-in calls by default!\n'
|
||||||
'If you need to use it please install `greenback` and set '
|
'If you need to use it please install `greenback` and set '
|
||||||
'`debug_mode=True` when opening the runtime '
|
'`debug_mode=True` when opening the runtime '
|
||||||
|
@ -133,7 +137,9 @@ async def open_root_actor(
|
||||||
)
|
)
|
||||||
|
|
||||||
sys.breakpointhook = block_bps
|
sys.breakpointhook = block_bps
|
||||||
# os.environ['PYTHONBREAKPOINT'] = None
|
# lol ok,
|
||||||
|
# https://docs.python.org/3/library/sys.html#sys.breakpointhook
|
||||||
|
os.environ['PYTHONBREAKPOINT'] = "0"
|
||||||
|
|
||||||
# attempt to retreive ``trio``'s sigint handler and stash it
|
# attempt to retreive ``trio``'s sigint handler and stash it
|
||||||
# on our debugger lock state.
|
# on our debugger lock state.
|
||||||
|
@ -203,6 +209,7 @@ async def open_root_actor(
|
||||||
):
|
):
|
||||||
loglevel = 'PDB'
|
loglevel = 'PDB'
|
||||||
|
|
||||||
|
|
||||||
elif debug_mode:
|
elif debug_mode:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Debug mode is only supported for the `trio` backend!"
|
"Debug mode is only supported for the `trio` backend!"
|
||||||
|
|
|
@ -692,21 +692,21 @@ class Actor:
|
||||||
proc: trio.Process
|
proc: trio.Process
|
||||||
_, proc, _ = entry
|
_, proc, _ = entry
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(poll := getattr(proc, 'poll', None))
|
(poll := getattr(proc, 'poll', None))
|
||||||
and poll() is None
|
and poll() is None
|
||||||
):
|
):
|
||||||
log.cancel(
|
log.cancel(
|
||||||
'Root actor reports no-more-peers, BUT\n'
|
'Root actor reports no-more-peers, BUT\n'
|
||||||
'a DISCONNECTED child still has the debug '
|
'a DISCONNECTED child still has the debug '
|
||||||
'lock!\n\n'
|
'lock!\n\n'
|
||||||
# f'root uid: {self.uid}\n'
|
# f'root uid: {self.uid}\n'
|
||||||
f'last disconnected child uid: {uid}\n'
|
f'last disconnected child uid: {uid}\n'
|
||||||
f'locking child uid: {pdb_user_uid}\n'
|
f'locking child uid: {pdb_user_uid}\n'
|
||||||
)
|
)
|
||||||
await maybe_wait_for_debugger(
|
await maybe_wait_for_debugger(
|
||||||
child_in_debug=True
|
child_in_debug=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: just bc a child's transport dropped
|
# TODO: just bc a child's transport dropped
|
||||||
# doesn't mean it's not still using the pdb
|
# doesn't mean it's not still using the pdb
|
||||||
|
@ -1140,7 +1140,6 @@ class Actor:
|
||||||
requester_type,
|
requester_type,
|
||||||
req_chan,
|
req_chan,
|
||||||
log_meth,
|
log_meth,
|
||||||
|
|
||||||
) = (
|
) = (
|
||||||
req_chan.uid,
|
req_chan.uid,
|
||||||
'peer',
|
'peer',
|
||||||
|
@ -1173,7 +1172,11 @@ class Actor:
|
||||||
# with the root actor in this tree
|
# with the root actor in this tree
|
||||||
debug_req = _debug.DebugStatus
|
debug_req = _debug.DebugStatus
|
||||||
lock_req_ctx: Context = debug_req.req_ctx
|
lock_req_ctx: Context = debug_req.req_ctx
|
||||||
if lock_req_ctx is not None:
|
if (
|
||||||
|
lock_req_ctx
|
||||||
|
and
|
||||||
|
lock_req_ctx.has_outcome
|
||||||
|
):
|
||||||
msg += (
|
msg += (
|
||||||
'-> Cancelling active debugger request..\n'
|
'-> Cancelling active debugger request..\n'
|
||||||
f'|_{_debug.Lock.repr()}\n\n'
|
f'|_{_debug.Lock.repr()}\n\n'
|
||||||
|
|
|
@ -43,6 +43,7 @@ from tractor._state import (
|
||||||
is_main_process,
|
is_main_process,
|
||||||
is_root_process,
|
is_root_process,
|
||||||
debug_mode,
|
debug_mode,
|
||||||
|
_runtime_vars,
|
||||||
)
|
)
|
||||||
from tractor.log import get_logger
|
from tractor.log import get_logger
|
||||||
from tractor._portal import Portal
|
from tractor._portal import Portal
|
||||||
|
@ -299,7 +300,6 @@ async def hard_kill(
|
||||||
|
|
||||||
|
|
||||||
async def soft_kill(
|
async def soft_kill(
|
||||||
|
|
||||||
proc: ProcessType,
|
proc: ProcessType,
|
||||||
wait_func: Callable[
|
wait_func: Callable[
|
||||||
[ProcessType],
|
[ProcessType],
|
||||||
|
@ -329,6 +329,18 @@ async def soft_kill(
|
||||||
await wait_func(proc)
|
await wait_func(proc)
|
||||||
|
|
||||||
except trio.Cancelled:
|
except trio.Cancelled:
|
||||||
|
with trio.CancelScope(shield=True):
|
||||||
|
await maybe_wait_for_debugger(
|
||||||
|
child_in_debug=_runtime_vars.get(
|
||||||
|
'_debug_mode', False
|
||||||
|
),
|
||||||
|
header_msg=(
|
||||||
|
'Delaying `soft_kill()` subproc reaper while debugger locked..\n'
|
||||||
|
),
|
||||||
|
# TODO: need a diff value then default?
|
||||||
|
# poll_steps=9999999,
|
||||||
|
)
|
||||||
|
|
||||||
# if cancelled during a soft wait, cancel the child
|
# if cancelled during a soft wait, cancel the child
|
||||||
# actor before entering the hard reap sequence
|
# actor before entering the hard reap sequence
|
||||||
# below. This means we try to do a graceful teardown
|
# below. This means we try to do a graceful teardown
|
||||||
|
|
|
@ -48,9 +48,11 @@ from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
)
|
)
|
||||||
from types import (
|
from types import (
|
||||||
|
FunctionType,
|
||||||
FrameType,
|
FrameType,
|
||||||
ModuleType,
|
ModuleType,
|
||||||
TracebackType,
|
TracebackType,
|
||||||
|
CodeType,
|
||||||
)
|
)
|
||||||
|
|
||||||
from msgspec import Struct
|
from msgspec import Struct
|
||||||
|
@ -90,43 +92,72 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
# XXX HACKZONE XXX
|
|
||||||
# hide exit stack frames on nurseries and cancel-scopes!
|
|
||||||
# |_ so avoid seeing it when the `pdbp` REPL is first engaged from
|
|
||||||
# inside a `trio.open_nursery()` scope (with no line after it
|
|
||||||
# in before the block end??).
|
|
||||||
#
|
|
||||||
# TODO: FINALLY got this workin originally with
|
|
||||||
# `@pdbp.hideframe` around the `wrapper()` def embedded inside
|
|
||||||
# `_ki_protection_decoratior()`.. which is in the module:
|
|
||||||
# /home/goodboy/.virtualenvs/tractor311/lib/python3.11/site-packages/trio/_core/_ki.py
|
|
||||||
#
|
|
||||||
# -[ ] make an issue and patch for `trio` core? maybe linked
|
|
||||||
# to the long outstanding `pdb` one below?
|
|
||||||
# |_ it's funny that there's frame hiding throughout `._run.py`
|
|
||||||
# but not where it matters on the below exit funcs..
|
|
||||||
#
|
|
||||||
# -[ ] provide a patchset for the lonstanding
|
|
||||||
# |_ https://github.com/python-trio/trio/issues/1155
|
|
||||||
#
|
|
||||||
# -[ ] make a linked issue to ^ and propose allowing all the
|
|
||||||
# `._core._run` code to have their `__tracebackhide__` value
|
|
||||||
# configurable by a `RunVar` to allow getting scheduler frames
|
|
||||||
# if desired through configuration?
|
|
||||||
#
|
|
||||||
# -[ ] maybe dig into the core `pdb` issue why the extra frame is shown
|
|
||||||
# at all?
|
|
||||||
#
|
|
||||||
pdbp.hideframe(trio._core._run.NurseryManager.__aexit__)
|
|
||||||
pdbp.hideframe(trio._core._run.CancelScope.__exit__)
|
|
||||||
pdbp.hideframe(_GeneratorContextManager.__exit__)
|
|
||||||
pdbp.hideframe(_AsyncGeneratorContextManager.__aexit__)
|
|
||||||
pdbp.hideframe(trio.Event.wait)
|
|
||||||
|
|
||||||
__all__ = [
|
def hide_runtime_frames() -> dict[FunctionType, CodeType]:
|
||||||
'breakpoint',
|
'''
|
||||||
'post_mortem',
|
Hide call-stack frames for various std-lib and `trio`-API primitives
|
||||||
]
|
such that the tracebacks presented from our runtime are as minimized
|
||||||
|
as possible, particularly from inside a `PdbREPL`.
|
||||||
|
|
||||||
|
'''
|
||||||
|
# XXX HACKZONE XXX
|
||||||
|
# hide exit stack frames on nurseries and cancel-scopes!
|
||||||
|
# |_ so avoid seeing it when the `pdbp` REPL is first engaged from
|
||||||
|
# inside a `trio.open_nursery()` scope (with no line after it
|
||||||
|
# in before the block end??).
|
||||||
|
#
|
||||||
|
# TODO: FINALLY got this workin originally with
|
||||||
|
# `@pdbp.hideframe` around the `wrapper()` def embedded inside
|
||||||
|
# `_ki_protection_decoratior()`.. which is in the module:
|
||||||
|
# /home/goodboy/.virtualenvs/tractor311/lib/python3.11/site-packages/trio/_core/_ki.py
|
||||||
|
#
|
||||||
|
# -[ ] make an issue and patch for `trio` core? maybe linked
|
||||||
|
# to the long outstanding `pdb` one below?
|
||||||
|
# |_ it's funny that there's frame hiding throughout `._run.py`
|
||||||
|
# but not where it matters on the below exit funcs..
|
||||||
|
#
|
||||||
|
# -[ ] provide a patchset for the lonstanding
|
||||||
|
# |_ https://github.com/python-trio/trio/issues/1155
|
||||||
|
#
|
||||||
|
# -[ ] make a linked issue to ^ and propose allowing all the
|
||||||
|
# `._core._run` code to have their `__tracebackhide__` value
|
||||||
|
# configurable by a `RunVar` to allow getting scheduler frames
|
||||||
|
# if desired through configuration?
|
||||||
|
#
|
||||||
|
# -[ ] maybe dig into the core `pdb` issue why the extra frame is shown
|
||||||
|
# at all?
|
||||||
|
#
|
||||||
|
funcs: list[FunctionType] = [
|
||||||
|
trio._core._run.NurseryManager.__aexit__,
|
||||||
|
trio._core._run.CancelScope.__exit__,
|
||||||
|
_GeneratorContextManager.__exit__,
|
||||||
|
_AsyncGeneratorContextManager.__aexit__,
|
||||||
|
_AsyncGeneratorContextManager.__aenter__,
|
||||||
|
trio.Event.wait,
|
||||||
|
]
|
||||||
|
func_list_str: str = textwrap.indent(
|
||||||
|
"\n".join(f.__qualname__ for f in funcs),
|
||||||
|
prefix=' |_ ',
|
||||||
|
)
|
||||||
|
log.devx(
|
||||||
|
'Hiding the following runtime frames by default:\n'
|
||||||
|
f'{func_list_str}\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
codes: dict[FunctionType, CodeType] = {}
|
||||||
|
for ref in funcs:
|
||||||
|
# stash a pre-modified version of each ref's code-obj
|
||||||
|
# so it can be reverted later if needed.
|
||||||
|
codes[ref] = ref.__code__
|
||||||
|
pdbp.hideframe(ref)
|
||||||
|
#
|
||||||
|
# pdbp.hideframe(trio._core._run.NurseryManager.__aexit__)
|
||||||
|
# pdbp.hideframe(trio._core._run.CancelScope.__exit__)
|
||||||
|
# pdbp.hideframe(_GeneratorContextManager.__exit__)
|
||||||
|
# pdbp.hideframe(_AsyncGeneratorContextManager.__aexit__)
|
||||||
|
# pdbp.hideframe(_AsyncGeneratorContextManager.__aenter__)
|
||||||
|
# pdbp.hideframe(trio.Event.wait)
|
||||||
|
return codes
|
||||||
|
|
||||||
|
|
||||||
class LockStatus(
|
class LockStatus(
|
||||||
|
@ -1032,15 +1063,24 @@ async def request_root_stdio_lock(
|
||||||
|
|
||||||
except (
|
except (
|
||||||
BaseException,
|
BaseException,
|
||||||
):
|
) as ctx_err:
|
||||||
log.exception(
|
message: str = (
|
||||||
'Failed during root TTY-lock dialog?\n'
|
'Failed during debug request dialog with root actor?\n\n'
|
||||||
f'{req_ctx}\n'
|
|
||||||
|
|
||||||
f'Cancelling IPC ctx!\n'
|
|
||||||
)
|
)
|
||||||
await req_ctx.cancel()
|
|
||||||
raise
|
if req_ctx:
|
||||||
|
message += (
|
||||||
|
f'{req_ctx}\n'
|
||||||
|
f'Cancelling IPC ctx!\n'
|
||||||
|
)
|
||||||
|
await req_ctx.cancel()
|
||||||
|
|
||||||
|
else:
|
||||||
|
message += 'Failed during `Portal.open_context()` ?\n'
|
||||||
|
|
||||||
|
log.exception(message)
|
||||||
|
ctx_err.add_note(message)
|
||||||
|
raise ctx_err
|
||||||
|
|
||||||
|
|
||||||
except (
|
except (
|
||||||
|
@ -1067,6 +1107,7 @@ async def request_root_stdio_lock(
|
||||||
# ctl-c out of the currently hanging task!
|
# ctl-c out of the currently hanging task!
|
||||||
raise DebugRequestError(
|
raise DebugRequestError(
|
||||||
'Failed to lock stdio from subactor IPC ctx!\n\n'
|
'Failed to lock stdio from subactor IPC ctx!\n\n'
|
||||||
|
|
||||||
f'req_ctx: {DebugStatus.req_ctx}\n'
|
f'req_ctx: {DebugStatus.req_ctx}\n'
|
||||||
) from req_err
|
) from req_err
|
||||||
|
|
||||||
|
@ -1777,7 +1818,7 @@ def _set_trace(
|
||||||
):
|
):
|
||||||
__tracebackhide__: bool = hide_tb
|
__tracebackhide__: bool = hide_tb
|
||||||
actor: tractor.Actor = actor or current_actor()
|
actor: tractor.Actor = actor or current_actor()
|
||||||
task: task or current_task()
|
task: trio.Task = task or current_task()
|
||||||
|
|
||||||
# else:
|
# else:
|
||||||
# TODO: maybe print the actor supervion tree up to the
|
# TODO: maybe print the actor supervion tree up to the
|
||||||
|
|
|
@ -53,17 +53,14 @@ LOG_FORMAT = (
|
||||||
|
|
||||||
DATE_FORMAT = '%b %d %H:%M:%S'
|
DATE_FORMAT = '%b %d %H:%M:%S'
|
||||||
|
|
||||||
LEVELS: dict[str, int] = {
|
# FYI, ERROR is 40
|
||||||
|
CUSTOM_LEVELS: dict[str, int] = {
|
||||||
'TRANSPORT': 5,
|
'TRANSPORT': 5,
|
||||||
'RUNTIME': 15,
|
'RUNTIME': 15,
|
||||||
'CANCEL': 16,
|
'CANCEL': 16,
|
||||||
'DEVX': 400,
|
'DEVX': 17,
|
||||||
'PDB': 500,
|
'PDB': 500,
|
||||||
}
|
}
|
||||||
# _custom_levels: set[str] = {
|
|
||||||
# lvlname.lower for lvlname in LEVELS.keys()
|
|
||||||
# }
|
|
||||||
|
|
||||||
STD_PALETTE = {
|
STD_PALETTE = {
|
||||||
'CRITICAL': 'red',
|
'CRITICAL': 'red',
|
||||||
'ERROR': 'red',
|
'ERROR': 'red',
|
||||||
|
@ -137,7 +134,7 @@ class StackLevelAdapter(LoggerAdapter):
|
||||||
"Developer experience" sub-sys statuses.
|
"Developer experience" sub-sys statuses.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
return self.log(400, msg)
|
return self.log(17, msg)
|
||||||
|
|
||||||
def log(
|
def log(
|
||||||
self,
|
self,
|
||||||
|
@ -154,8 +151,7 @@ class StackLevelAdapter(LoggerAdapter):
|
||||||
if self.isEnabledFor(level):
|
if self.isEnabledFor(level):
|
||||||
stacklevel: int = 3
|
stacklevel: int = 3
|
||||||
if (
|
if (
|
||||||
level in LEVELS.values()
|
level in CUSTOM_LEVELS.values()
|
||||||
# or level in _custom_levels
|
|
||||||
):
|
):
|
||||||
stacklevel: int = 4
|
stacklevel: int = 4
|
||||||
|
|
||||||
|
@ -202,7 +198,8 @@ class StackLevelAdapter(LoggerAdapter):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# TODO IDEA:
|
# TODO IDEAs:
|
||||||
|
# -[ ] move to `.devx.pformat`?
|
||||||
# -[ ] do per task-name and actor-name color coding
|
# -[ ] do per task-name and actor-name color coding
|
||||||
# -[ ] unique color per task-id and actor-uuid
|
# -[ ] unique color per task-id and actor-uuid
|
||||||
def pformat_task_uid(
|
def pformat_task_uid(
|
||||||
|
@ -309,7 +306,7 @@ def get_logger(
|
||||||
logger = StackLevelAdapter(log, ActorContextInfo())
|
logger = StackLevelAdapter(log, ActorContextInfo())
|
||||||
|
|
||||||
# additional levels
|
# additional levels
|
||||||
for name, val in LEVELS.items():
|
for name, val in CUSTOM_LEVELS.items():
|
||||||
logging.addLevelName(val, name)
|
logging.addLevelName(val, name)
|
||||||
|
|
||||||
# ensure customs levels exist as methods
|
# ensure customs levels exist as methods
|
||||||
|
@ -377,7 +374,7 @@ def at_least_level(
|
||||||
|
|
||||||
'''
|
'''
|
||||||
if isinstance(level, str):
|
if isinstance(level, str):
|
||||||
level: int = LEVELS[level.upper()]
|
level: int = CUSTOM_LEVELS[level.upper()]
|
||||||
|
|
||||||
if log.getEffectiveLevel() <= level:
|
if log.getEffectiveLevel() <= level:
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -162,7 +162,10 @@ class MsgDec(Struct):
|
||||||
# TODO: would get moved into `FieldSpec.__str__()` right?
|
# TODO: would get moved into `FieldSpec.__str__()` right?
|
||||||
@property
|
@property
|
||||||
def spec_str(self) -> str:
|
def spec_str(self) -> str:
|
||||||
return pformat_msgspec(codec=self)
|
return pformat_msgspec(
|
||||||
|
codec=self,
|
||||||
|
join_char='|',
|
||||||
|
)
|
||||||
|
|
||||||
pld_spec_str = spec_str
|
pld_spec_str = spec_str
|
||||||
|
|
||||||
|
@ -211,7 +214,7 @@ def mk_msgspec_table(
|
||||||
msgtypes = [msgspec]
|
msgtypes = [msgspec]
|
||||||
|
|
||||||
msgt_table: dict[str, MsgType] = {
|
msgt_table: dict[str, MsgType] = {
|
||||||
msgt: str(msgt)
|
msgt: str(msgt.__name__)
|
||||||
for msgt in msgtypes
|
for msgt in msgtypes
|
||||||
}
|
}
|
||||||
if msg:
|
if msg:
|
||||||
|
|
Loading…
Reference in New Issue