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.log import get_logger | ||||
| from tractor.msg import ( | ||||
|     Aid, | ||||
|     Error, | ||||
|     PayloadMsg, | ||||
|     MsgType, | ||||
|  | @ -479,8 +480,9 @@ class RemoteActorError(Exception): | |||
| 
 | ||||
|     @property | ||||
|     def relay_uid(self) -> tuple[str, str]|None: | ||||
|         if msg := self._ipc_msg: | ||||
|             return tuple( | ||||
|             self._ipc_msg.relay_path[-1] | ||||
|                 msg.relay_path[-1] | ||||
|             ) | ||||
| 
 | ||||
|     @property | ||||
|  | @ -521,7 +523,8 @@ class RemoteActorError(Exception): | |||
|         for key in fields: | ||||
|             if ( | ||||
|                 key == 'relay_uid' | ||||
|                 and not self.is_inception() | ||||
|                 and | ||||
|                 not self.is_inception() | ||||
|             ): | ||||
|                 continue | ||||
| 
 | ||||
|  | @ -534,6 +537,13 @@ class RemoteActorError(Exception): | |||
|                     None, | ||||
|                 ) | ||||
|             ) | ||||
|             if ( | ||||
|                 key == 'canceller' | ||||
|                 and | ||||
|                 isinstance(val, Aid) | ||||
|             ): | ||||
|                 val: str = val.reprol(sin_uuid=False) | ||||
| 
 | ||||
|             # TODO: for `.relay_path` on multiline? | ||||
|             # if not isinstance(val, str): | ||||
|             #     val_str = pformat(val) | ||||
|  | @ -623,12 +633,19 @@ class RemoteActorError(Exception): | |||
|         # IFF there is an embedded traceback-str we always | ||||
|         # draw the ascii-box around it. | ||||
|         body: str = '' | ||||
|         if tb_str := self.tb_str: | ||||
|         fields: str = self._mk_fields_str( | ||||
|             _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 ( | ||||
|             pformat_boxed_tb, | ||||
|         ) | ||||
|  | @ -640,7 +657,7 @@ class RemoteActorError(Exception): | |||
|             # just after <Type( | ||||
|             #             |___ .. | ||||
|             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 | ||||
|  | @ -713,6 +730,10 @@ class RemoteActorError(Exception): | |||
| 
 | ||||
| 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 | ||||
|     ``Portal.cancel_actor()`` or ``Context.cancel()``. | ||||
| 
 | ||||
|  | @ -737,8 +758,8 @@ class ContextCancelled(RemoteActorError): | |||
| 
 | ||||
|         - (simulating) an IPC transport network outage | ||||
|         - a (malicious) pkt sent specifically to cancel an actor's | ||||
|           runtime non-gracefully without ensuring ongoing RPC tasks are  | ||||
|           incrementally cancelled as is done with: | ||||
|           runtime non-gracefully without ensuring ongoing RPC tasks | ||||
|           are incrementally cancelled as is done with: | ||||
|           `Actor` | ||||
|           |_`.cancel()` | ||||
|           |_`.cancel_soon()` | ||||
|  | @ -759,6 +780,59 @@ class ContextCancelled(RemoteActorError): | |||
|     # 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( | ||||
|     RemoteActorError, | ||||
| ): | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue