diff --git a/NEWS.rst b/NEWS.rst index 8ba852c3..8894725c 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -78,6 +78,7 @@ Bug Fixes for ``asyncio``-side errors to not propagate due to a race condition. The implementation fix summary is: + - add state to signal the end of the ``trio`` side task to be read by the ``asyncio`` side and always cancel any ongoing task in such cases. diff --git a/tractor/_context.py b/tractor/_context.py index 4e81e7c7..d538e361 100644 --- a/tractor/_context.py +++ b/tractor/_context.py @@ -135,19 +135,21 @@ class Context: communication context. (We've also considered other names and ideas: - - "communicating tasks scope": cts - - "distributed task scope": dts - - "communicating tasks context": ctc - **Got a better idea for naming? Make an issue dawg!** + - "communicating tasks scope": cts + - "distributed task scope": dts + - "communicating tasks context": ctc + + **Got a better idea for naming? Make an issue dawg!** ) NB: This class should **never be instatiated directly**, it is allocated by the runtime in 2 ways: - - by entering `Portal.open_context()` which is the primary - public API for any "parent" task or, - - by the RPC machinery's `._rpc._invoke()` as a `ctx` arg - to a remotely scheduled "child" function. + + - by entering `Portal.open_context()` which is the primary + public API for any "parent" task or, + - by the RPC machinery's `._rpc._invoke()` as a `ctx` arg + to a remotely scheduled "child" function. AND is always constructed using the below `mk_context()`. @@ -443,6 +445,7 @@ class Context: ''' Records whether cancellation has been requested for this context by a call to `.cancel()` either due to, + - an explicit call by some local task, - or an implicit call due to an error caught inside the `Portal.open_context()` block. @@ -554,6 +557,7 @@ class Context: Only as an FYI, in the "child" side case it can also be set but never is readable by any task outside the RPC machinery in `._invoke()` since,: + - when a child side calls `.cancel()`, `._scope.cancel()` is called immediately and handled specially inside `._invoke()` to raise a `ContextCancelled` which is then @@ -1033,8 +1037,8 @@ class Context: `._scope.cancel()` and delivering an `ContextCancelled` ack msg in reponse. - Behaviour: - --------- + **Behaviour:** + - after the far end cancels, the `.cancel()` calling side should receive a `ContextCancelled` with the `.canceller: tuple` uid set to the current `Actor.aid.uid`. @@ -1505,7 +1509,8 @@ class Context: TODO->( this is doc-driven-dev content not yet actual ;P ) The final "outcome" from an IPC context which can be any of: - - some `outcome.Value` which boxes the returned output from the peer task's + + - some `outcome.Value` which boxes the returned output from the peer task's `@context`-decorated remote task-as-func, or - an `outcome.Error` wrapping an exception raised that same RPC task after a fault or cancellation, or @@ -2066,7 +2071,7 @@ async def open_context_from_portal( allows for deterministic setup and teardown of a remotely scheduled task in another remote actor. Once opened, the 2 now "linked" tasks run completely in parallel in each actor's - runtime with their enclosing `trio.CancelScope`s kept in + runtime with their enclosing `trio.CancelScope`\\ s kept in a synced state wherein if either side errors or cancels an equivalent error is relayed to the other side via an SC-compat IPC protocol. diff --git a/tractor/_exceptions.py b/tractor/_exceptions.py index 3b087184..e4292f06 100644 --- a/tractor/_exceptions.py +++ b/tractor/_exceptions.py @@ -823,12 +823,13 @@ 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: - `Actor` - |_`.cancel()` - |_`.cancel_soon()` - |_`._cancel_task()` + runtime non-gracefully without ensuring ongoing RPC tasks are + incrementally cancelled as is done with:: + + `Actor` + |_`.cancel()` + |_`.cancel_soon()` + |_`._cancel_task()` ''' value: tuple[str, str]|None = self._ipc_msg.canceller diff --git a/tractor/devx/debug/_post_mortem.py b/tractor/devx/debug/_post_mortem.py index 6b541218..56bd01db 100644 --- a/tractor/devx/debug/_post_mortem.py +++ b/tractor/devx/debug/_post_mortem.py @@ -321,6 +321,7 @@ def open_crash_handler( `typer` users so they can quickly wrap cmd endpoints which get automatically wrapped to use the runtime's `debug_mode: bool` AND `pdbp.pm()` around any code that is PRE-runtime entry + - any sync code which runs BEFORE the main call to `trio.run()`. diff --git a/tractor/devx/debug/_repl.py b/tractor/devx/debug/_repl.py index 5fba13d2..20cdef22 100644 --- a/tractor/devx/debug/_repl.py +++ b/tractor/devx/debug/_repl.py @@ -187,7 +187,7 @@ def mk_pdb() -> PdbREPL: FURTHER, the `pdbp.Pdb` instance is configured to be `trio` "compatible" from a SIGINT handling perspective; we mask out - the default `pdb` handler and instead apply `trio`s default + the default `pdb` handler and instead apply `trio`\\ s default which mostly addresses all issues described in: - https://github.com/python-trio/trio/issues/1155 diff --git a/tractor/discovery/_api.py b/tractor/discovery/_api.py index 1d7108f0..ec559baa 100644 --- a/tractor/discovery/_api.py +++ b/tractor/discovery/_api.py @@ -253,6 +253,7 @@ async def query_actor( listening @ `regaddr`. Yields a `tuple` of `(addr, reg_portal)` where, + - `addr` is the transport protocol (socket) address or `None` if no entry under that name exists, - `reg_portal` is the `Portal` (or `LocalPortal` when the @@ -439,7 +440,7 @@ async def wait_for_actor( ) -> AsyncGenerator[Portal, None]: ''' Wait on at least one peer actor to register `name` with the - registrar, yield a `Portal to the first registree. + registrar, yield a `Portal` to the first registree. ''' actor: Actor = current_actor() diff --git a/tractor/msg/_codec.py b/tractor/msg/_codec.py index e8a0379e..26f8e24e 100644 --- a/tractor/msg/_codec.py +++ b/tractor/msg/_codec.py @@ -371,7 +371,7 @@ class MsgCodec(Struct): A IPC msg interchange format lib's encoder + decoder pair. Pretty much nothing more then delegation to underlying - `msgspec..Encoder/Decoder`s for now. + `msgspec..Encoder/Decoder`\\ s for now. ''' _enc: msgpack.Encoder diff --git a/tractor/msg/types.py b/tractor/msg/types.py index ea314f00..4f3e33cc 100644 --- a/tractor/msg/types.py +++ b/tractor/msg/types.py @@ -453,7 +453,7 @@ class Error( # omit_defaults=True, ): ''' - A pkt that wraps `RemoteActorError`s for relay and raising. + A pkt that wraps `RemoteActorError`\\ s for relay and raising. Fields are 1-to-1 meta-data as needed originally by `RemoteActorError.msgdata: dict` but now are defined here. diff --git a/tractor/runtime/_supervise.py b/tractor/runtime/_supervise.py index d488ca0c..f5c5e786 100644 --- a/tractor/runtime/_supervise.py +++ b/tractor/runtime/_supervise.py @@ -252,6 +252,7 @@ class ActorNursery: ''' Records whether cancellation has been requested for this actor-nursery by a call to `.cancel()` either due to, + - an explicit call by some actor-local-task, - an implicit call due to an error/cancel emited inside the `tractor.open_nursery()` block. diff --git a/tractor/trionics/_mngrs.py b/tractor/trionics/_mngrs.py index 601d196d..d4f986c3 100644 --- a/tractor/trionics/_mngrs.py +++ b/tractor/trionics/_mngrs.py @@ -120,12 +120,12 @@ async def gather_contexts( None, ]: ''' - Concurrently enter a sequence of async context managers (`acm`s), + Concurrently enter a sequence of async context managers (`acm`\\ s), each scheduled in a separate `trio.Task` and deliver their - unwrapped `yield`-ed values in the same order once all `@acm`s + unwrapped `yield`-ed values in the same order once all `@acm`\\ s in every task have entered. - On exit, all `acm`s are subsequently and concurrently exited with + On exit, all `acm`\\ s are subsequently and concurrently exited with **no order guarantees**. This function is somewhat similar to a batch of non-blocking