Add `ActorCancelled` as an runtime-wide-signal
As in a layer "above" a KBI/SIGINT but "below" a `ContextCancelled` and generally signalling an interrupt which requests cancellation of the actor's `trio.run()`. Impl deats, - mk the new exc type inherit from our ctxc (for now) but overriding the `.canceller` impl to, * pull from the `RemoteActorError._extra_msgdata: dict` when no `._ipc_msg` is set (which is always to start, until we incorporate a new `CancelActor` msg type). * not allow a `None` value since we should key-error if not set per prev bullet. - Mk adjustments (related) to parent `RemoteActorError.pformat()` to accommodate showing the `.canceller` field in repr output, * change `.relay_uid` to not crash when `._ipc_msg` is unset. * support `.msg.types.Aid` and use its `.reprol()` from `._mk_fields_str()`. * always call `._mk_fields_str()`, not just when `tb_str` is provided, and for now use any `._message` in-place of a `tb_str` when undefined.actor_cancelled_exc_type
parent
5ab642bdf0
commit
dc806b8aba
|
@ -46,6 +46,7 @@ from msgspec import (
|
||||||
from tractor._state import current_actor
|
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 (
|
||||||
|
Aid,
|
||||||
Error,
|
Error,
|
||||||
PayloadMsg,
|
PayloadMsg,
|
||||||
MsgType,
|
MsgType,
|
||||||
|
@ -479,8 +480,9 @@ class RemoteActorError(Exception):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def relay_uid(self) -> tuple[str, str]|None:
|
def relay_uid(self) -> tuple[str, str]|None:
|
||||||
|
if msg := self._ipc_msg:
|
||||||
return tuple(
|
return tuple(
|
||||||
self._ipc_msg.relay_path[-1]
|
msg.relay_path[-1]
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -521,7 +523,8 @@ class RemoteActorError(Exception):
|
||||||
for key in fields:
|
for key in fields:
|
||||||
if (
|
if (
|
||||||
key == 'relay_uid'
|
key == 'relay_uid'
|
||||||
and not self.is_inception()
|
and
|
||||||
|
not self.is_inception()
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -534,6 +537,13 @@ class RemoteActorError(Exception):
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if (
|
||||||
|
key == 'canceller'
|
||||||
|
and
|
||||||
|
isinstance(val, Aid)
|
||||||
|
):
|
||||||
|
val: str = val.reprol(sin_uuid=False)
|
||||||
|
|
||||||
# TODO: for `.relay_path` on multiline?
|
# TODO: for `.relay_path` on multiline?
|
||||||
# if not isinstance(val, str):
|
# if not isinstance(val, str):
|
||||||
# val_str = pformat(val)
|
# val_str = pformat(val)
|
||||||
|
@ -623,12 +633,19 @@ class RemoteActorError(Exception):
|
||||||
# IFF there is an embedded traceback-str we always
|
# IFF there is an embedded traceback-str we always
|
||||||
# draw the ascii-box around it.
|
# draw the ascii-box around it.
|
||||||
body: str = ''
|
body: str = ''
|
||||||
if tb_str := self.tb_str:
|
|
||||||
fields: str = self._mk_fields_str(
|
fields: str = self._mk_fields_str(
|
||||||
_body_fields
|
_body_fields
|
||||||
+
|
+
|
||||||
self.extra_body_fields,
|
self.extra_body_fields,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ?TODO, ensure the `.message` doesn't show up 2x in
|
||||||
|
# output ya?
|
||||||
|
tb_str: str = (
|
||||||
|
self.tb_str
|
||||||
|
or
|
||||||
|
self._message
|
||||||
|
)
|
||||||
from tractor.devx import (
|
from tractor.devx import (
|
||||||
pformat_boxed_tb,
|
pformat_boxed_tb,
|
||||||
)
|
)
|
||||||
|
@ -640,7 +657,7 @@ class RemoteActorError(Exception):
|
||||||
# just after <Type(
|
# just after <Type(
|
||||||
# |___ ..
|
# |___ ..
|
||||||
tb_body_indent=1,
|
tb_body_indent=1,
|
||||||
boxer_header=self.relay_uid,
|
boxer_header=self.relay_uid or '-',
|
||||||
)
|
)
|
||||||
|
|
||||||
# !TODO, it'd be nice to import these top level without
|
# !TODO, it'd be nice to import these top level without
|
||||||
|
@ -713,6 +730,10 @@ class RemoteActorError(Exception):
|
||||||
|
|
||||||
class ContextCancelled(RemoteActorError):
|
class ContextCancelled(RemoteActorError):
|
||||||
'''
|
'''
|
||||||
|
IPC context cancellation signal/msg.
|
||||||
|
|
||||||
|
Often reffed with the short-hand: "ctxc".
|
||||||
|
|
||||||
Inter-actor task context was cancelled by either a call to
|
Inter-actor task context was cancelled by either a call to
|
||||||
``Portal.cancel_actor()`` or ``Context.cancel()``.
|
``Portal.cancel_actor()`` or ``Context.cancel()``.
|
||||||
|
|
||||||
|
@ -737,8 +758,8 @@ class ContextCancelled(RemoteActorError):
|
||||||
|
|
||||||
- (simulating) an IPC transport network outage
|
- (simulating) an IPC transport network outage
|
||||||
- a (malicious) pkt sent specifically to cancel an actor's
|
- a (malicious) pkt sent specifically to cancel an actor's
|
||||||
runtime non-gracefully without ensuring ongoing RPC tasks are
|
runtime non-gracefully without ensuring ongoing RPC tasks
|
||||||
incrementally cancelled as is done with:
|
are incrementally cancelled as is done with:
|
||||||
`Actor`
|
`Actor`
|
||||||
|_`.cancel()`
|
|_`.cancel()`
|
||||||
|_`.cancel_soon()`
|
|_`.cancel_soon()`
|
||||||
|
@ -759,6 +780,59 @@ class ContextCancelled(RemoteActorError):
|
||||||
# src_actor_uid = canceller
|
# src_actor_uid = canceller
|
||||||
|
|
||||||
|
|
||||||
|
class ActorCancelled(ContextCancelled):
|
||||||
|
'''
|
||||||
|
Runtime-layer cancellation signal/msg.
|
||||||
|
|
||||||
|
Indicates a "graceful interrupt" of the machinery scheduled by
|
||||||
|
the py-proc's `trio.run()`.
|
||||||
|
|
||||||
|
Often reffed with the short-hand: "actorc".
|
||||||
|
|
||||||
|
Raised from within `an: ActorNursery` (via an `ExceptionGroup`)
|
||||||
|
when an actor has been "process wide" cancel-called using any of,
|
||||||
|
|
||||||
|
- `ActorNursery.cancel()`
|
||||||
|
- `Portal.cancel_actor()`
|
||||||
|
|
||||||
|
**and** that cancel request was part of a "non graceful" cancel
|
||||||
|
condition.
|
||||||
|
|
||||||
|
That is, whenever an exception is to be raised outside an `an`
|
||||||
|
scope-block due to some error raised-in/relayed-to that scope. In
|
||||||
|
such cases for every subactor which was cancelledand subsequently
|
||||||
|
( and according to the `an`'s supervision strat ) this is
|
||||||
|
normally raised per subactor portal.
|
||||||
|
|
||||||
|
'''
|
||||||
|
@property
|
||||||
|
def canceller(self) -> Aid:
|
||||||
|
'''
|
||||||
|
Return the (maybe) `Actor.aid: Aid` for the requesting-author
|
||||||
|
of this actorc.
|
||||||
|
|
||||||
|
Emit a warning msg when `.canceller` has not been set.
|
||||||
|
|
||||||
|
See additional relevant notes in
|
||||||
|
`ContextCancelled.canceller`.
|
||||||
|
|
||||||
|
'''
|
||||||
|
value: tuple[str, str]|None
|
||||||
|
if msg := self._ipc_msg:
|
||||||
|
value = msg.canceller
|
||||||
|
else:
|
||||||
|
value = self._extra_msgdata['canceller']
|
||||||
|
|
||||||
|
if value:
|
||||||
|
return value
|
||||||
|
|
||||||
|
log.warning(
|
||||||
|
'IPC Context cancelled without a requesting actor?\n'
|
||||||
|
'Maybe the IPC transport ended abruptly?\n\n'
|
||||||
|
f'{self}'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MsgTypeError(
|
class MsgTypeError(
|
||||||
RemoteActorError,
|
RemoteActorError,
|
||||||
):
|
):
|
||||||
|
|
Loading…
Reference in New Issue