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
							
								
									83ce2275b9
								
							
						
					
					
						commit
						2dc13a3304
					
				|  | @ -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