Polish the non-debug docs examples
Finish the Python style, typing and docstring pass across the ordinary, parallelism, Trio and integration examples. Preserve each demo's runtime behavior while tightening callable, portal, stream and nursery annotations. Use the modern `.chan` portal attr and current actor-lifecycle terminology. (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))wkt/big_boi_docs_472_follow_ups
parent
0be872ff97
commit
96a1838210
|
|
@ -1,29 +1,40 @@
|
|||
import trio
|
||||
import tractor
|
||||
|
||||
_this_module = __name__
|
||||
the_line = 'Hi my name is {}'
|
||||
_this_module: str = __name__
|
||||
the_line: str = 'Hi my name is {}'
|
||||
|
||||
|
||||
tractor.log.get_console_log("INFO")
|
||||
tractor.log.get_console_log('INFO')
|
||||
|
||||
|
||||
async def hi() -> str:
|
||||
'''
|
||||
Return a greeting naming the current actor.
|
||||
|
||||
'''
|
||||
return the_line.format(tractor.current_actor().name)
|
||||
|
||||
|
||||
async def say_hello(other_actor: str) -> str:
|
||||
'''
|
||||
Ask another actor to return its greeting.
|
||||
|
||||
'''
|
||||
portal: tractor.Portal
|
||||
async with tractor.wait_for_actor(other_actor) as portal:
|
||||
return await portal.run(hi)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Main tractor entry point, the "master" process (for now
|
||||
'''
|
||||
Main tractor entry point, the "master" process (for now
|
||||
acts as the "director").
|
||||
"""
|
||||
|
||||
'''
|
||||
an: tractor.ActorNursery
|
||||
async with tractor.open_nursery() as an:
|
||||
print("Alright... Action!")
|
||||
print('Alright... Action!')
|
||||
|
||||
# both actors wait on (then dial!) the *other*, so each
|
||||
# must outlive both hellos: spawn as daemons, run the
|
||||
|
|
@ -40,6 +51,10 @@ async def main() -> None:
|
|||
name: str,
|
||||
other_actor: str,
|
||||
) -> None:
|
||||
'''
|
||||
Print a greeting fetched through a named actor.
|
||||
|
||||
'''
|
||||
print(
|
||||
# RPC through an existing actor's `Portal`.
|
||||
await portals[name].run(
|
||||
|
|
@ -48,13 +63,14 @@ async def main() -> None:
|
|||
)
|
||||
)
|
||||
|
||||
tn: trio.Nursery
|
||||
async with trio.open_nursery() as tn:
|
||||
tn.start_soon(run_and_print, 'donny', 'gretchen')
|
||||
tn.start_soon(run_and_print, 'gretchen', 'donny')
|
||||
|
||||
await an.cancel()
|
||||
|
||||
print("CUTTTT CUUTT CUT!!! Donny!! You're supposed to say...")
|
||||
print('CUTTTT CUUTT CUT!!! Donny!! You\'re supposed to say...')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -3,13 +3,19 @@ import tractor
|
|||
|
||||
|
||||
async def cellar_door() -> str:
|
||||
'''
|
||||
Return a phrase from a spawned actor.
|
||||
|
||||
'''
|
||||
assert not tractor.is_root_process()
|
||||
return "Dang that's beautiful"
|
||||
return 'Dang that\'s beautiful'
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""The main ``tractor`` routine.
|
||||
"""
|
||||
'''
|
||||
The main ``tractor`` routine.
|
||||
|
||||
'''
|
||||
# spawn a subactor, run ``cellar_door()`` as its lone task,
|
||||
# block until its result arrives and the subactor is reaped.
|
||||
print(
|
||||
|
|
|
|||
|
|
@ -3,15 +3,20 @@ import tractor
|
|||
|
||||
|
||||
async def movie_theatre_question() -> str:
|
||||
"""A question asked in a dark theatre, in a tangent
|
||||
'''
|
||||
A question asked in a dark theatre, in a tangent
|
||||
(errr, I mean different) process.
|
||||
"""
|
||||
|
||||
'''
|
||||
return 'have you ever seen a portal?'
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""The main ``tractor`` routine.
|
||||
"""
|
||||
'''
|
||||
The main ``tractor`` routine.
|
||||
|
||||
'''
|
||||
an: tractor.ActorNursery
|
||||
async with tractor.open_nursery() as an:
|
||||
|
||||
portal: tractor.Portal = await an.start_actor(
|
||||
|
|
@ -24,9 +29,8 @@ async def main() -> None:
|
|||
# call the subactor a 2nd time
|
||||
print(await portal.run(movie_theatre_question))
|
||||
|
||||
# the async with will block here indefinitely waiting
|
||||
# for our actor "frank" to complete, but since it's an
|
||||
# "outlive_main" actor it will never end until cancelled
|
||||
# the async with will wait indefinitely for "frank" because
|
||||
# its runtime remains active until explicitly cancelled
|
||||
await portal.cancel_actor()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,31 @@
|
|||
from typing import AsyncIterator
|
||||
from itertools import repeat
|
||||
from typing import AsyncIterator
|
||||
|
||||
import trio
|
||||
import tractor
|
||||
|
||||
|
||||
async def stream_forever() -> AsyncIterator[int]:
|
||||
async def stream_forever() -> AsyncIterator[str]:
|
||||
'''
|
||||
Stream the same message indefinitely.
|
||||
|
||||
for i in repeat("I can see these little future bubble things"):
|
||||
# each yielded value is sent over the ``Channel`` to the parent actor
|
||||
yield i
|
||||
'''
|
||||
message: str
|
||||
for message in repeat(
|
||||
'I can see these little future bubble things',
|
||||
):
|
||||
# each yielded value is sent over the ``Channel`` to the
|
||||
# parent actor
|
||||
yield message
|
||||
await trio.sleep(0.01)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
'''
|
||||
Print messages streamed from a subactor.
|
||||
|
||||
'''
|
||||
an: tractor.ActorNursery
|
||||
async with tractor.open_nursery() as an:
|
||||
|
||||
portal: tractor.Portal = await an.start_actor(
|
||||
|
|
@ -24,10 +35,12 @@ async def main() -> None:
|
|||
|
||||
# this async for loop streams values from the above
|
||||
# async generator running in a separate process
|
||||
stream: tractor.MsgStream
|
||||
async with portal.open_stream_from(stream_forever) as stream:
|
||||
count: int = 0
|
||||
async for letter in stream:
|
||||
print(letter)
|
||||
message: str
|
||||
async for message in stream:
|
||||
print(message)
|
||||
count += 1
|
||||
|
||||
if count > 50:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import time
|
||||
from typing import AsyncIterator
|
||||
|
||||
import trio
|
||||
import tractor
|
||||
from tractor import (
|
||||
|
|
@ -9,14 +11,19 @@ from tractor import (
|
|||
|
||||
|
||||
# this is the first 2 actors, streamer_1 and streamer_2
|
||||
async def stream_data(seed: int):
|
||||
async def stream_data(seed: int) -> AsyncIterator[int]:
|
||||
'''
|
||||
Stream integers up to a seed value.
|
||||
|
||||
'''
|
||||
i: int
|
||||
for i in range(seed):
|
||||
yield i
|
||||
await trio.sleep(0.0001) # trigger scheduler
|
||||
|
||||
|
||||
# this is the third actor; the aggregator
|
||||
async def aggregate(seed: int):
|
||||
async def aggregate(seed: int) -> AsyncIterator[int]:
|
||||
'''
|
||||
Ensure that the two streams we receive match but only stream
|
||||
a single set of values to the parent.
|
||||
|
|
@ -25,6 +32,7 @@ async def aggregate(seed: int):
|
|||
an: ActorNursery
|
||||
async with tractor.open_nursery() as an:
|
||||
portals: list[Portal] = []
|
||||
i: int
|
||||
for i in range(1, 3):
|
||||
|
||||
# fork/spawn call
|
||||
|
|
@ -35,20 +43,35 @@ async def aggregate(seed: int):
|
|||
|
||||
portals.append(portal)
|
||||
|
||||
send_chan: trio.MemorySendChannel[int]
|
||||
recv_chan: trio.MemoryReceiveChannel[int]
|
||||
send_chan, recv_chan = trio.open_memory_channel(500)
|
||||
|
||||
async def push_to_chan(portal: Portal, send_chan):
|
||||
async def push_to_chan(
|
||||
portal: Portal,
|
||||
send_chan: trio.MemorySendChannel[int],
|
||||
) -> None:
|
||||
'''
|
||||
Forward one remote stream into a local channel.
|
||||
|
||||
'''
|
||||
# TODO: https://github.com/goodboy/tractor/issues/207
|
||||
async with send_chan:
|
||||
async with portal.open_stream_from(stream_data, seed=seed) as stream:
|
||||
stream: MsgStream
|
||||
async with portal.open_stream_from(
|
||||
stream_data,
|
||||
seed=seed,
|
||||
) as stream:
|
||||
value: int
|
||||
async for value in stream:
|
||||
# leverage trio's built-in backpressure
|
||||
await send_chan.send(value)
|
||||
|
||||
print(f"FINISHED ITERATING {portal.channel.uid}")
|
||||
uid: tuple[str, str] = portal.chan.uid
|
||||
print(f'FINISHED ITERATING {uid}')
|
||||
|
||||
# spawn 2 trio tasks to collect streams and push to a local queue
|
||||
# spawn 2 trio tasks to collect streams and push to a local
|
||||
# queue
|
||||
n: trio.Nursery
|
||||
async with trio.open_nursery() as n:
|
||||
|
||||
|
|
@ -62,8 +85,9 @@ async def aggregate(seed: int):
|
|||
# close this local task's reference to send side
|
||||
await send_chan.aclose()
|
||||
|
||||
unique_vals = set()
|
||||
unique_vals: set[int] = set()
|
||||
async with recv_chan:
|
||||
value: int
|
||||
async for value in recv_chan:
|
||||
if value not in unique_vals:
|
||||
unique_vals.add(value)
|
||||
|
|
@ -72,11 +96,11 @@ async def aggregate(seed: int):
|
|||
|
||||
assert value in unique_vals
|
||||
|
||||
print("FINISHED ITERATING in aggregator")
|
||||
print('FINISHED ITERATING in aggregator')
|
||||
|
||||
await an.cancel()
|
||||
print("WAITING on `ActorNursery` to finish")
|
||||
print("AGGREGATOR COMPLETE!")
|
||||
print('WAITING on `ActorNursery` to finish')
|
||||
print('AGGREGATOR COMPLETE!')
|
||||
|
||||
|
||||
async def main() -> list[int]:
|
||||
|
|
@ -95,8 +119,8 @@ async def main() -> list[int]:
|
|||
# debug_mode=True,
|
||||
) as an:
|
||||
|
||||
seed = int(1e3)
|
||||
pre_start = time.time()
|
||||
seed: int = int(1e3)
|
||||
pre_start: float = time.time()
|
||||
|
||||
portal: Portal = await an.start_actor(
|
||||
name='aggregator',
|
||||
|
|
@ -109,23 +133,27 @@ async def main() -> list[int]:
|
|||
seed=seed,
|
||||
) as stream:
|
||||
|
||||
start = time.time()
|
||||
start: float = time.time()
|
||||
# the portal call returns exactly what you'd expect
|
||||
# as if the remote "aggregate" function was called locally
|
||||
# as if the remote "aggregate" function was called
|
||||
# locally
|
||||
result_stream: list[int] = []
|
||||
value: int
|
||||
async for value in stream:
|
||||
result_stream.append(value)
|
||||
|
||||
cancelled: bool = await portal.cancel_actor()
|
||||
assert cancelled
|
||||
|
||||
stream_time: float = time.time() - start
|
||||
total_time: float = time.time() - pre_start
|
||||
print(
|
||||
f"STREAM TIME = {time.time() - start}\n"
|
||||
f"STREAM + SPAWN TIME = {time.time() - pre_start}\n"
|
||||
f'STREAM TIME = {stream_time}\n'
|
||||
f'STREAM + SPAWN TIME = {total_time}\n'
|
||||
)
|
||||
assert result_stream == list(range(seed))
|
||||
return result_stream
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
final_stream = trio.run(main)
|
||||
final_stream: list[int] = trio.run(main)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ import tractor
|
|||
async def aio_echo_server(
|
||||
chan: tractor.to_asyncio.LinkedTaskChannel,
|
||||
) -> None:
|
||||
'''
|
||||
Echo messages received through an asyncio task channel.
|
||||
|
||||
'''
|
||||
# a first message must be sent **from** this ``asyncio``
|
||||
# task or the ``trio`` side will never unblock from
|
||||
# ``tractor.to_asyncio.open_channel_from():``
|
||||
|
|
@ -29,8 +32,14 @@ async def aio_echo_server(
|
|||
async def trio_to_aio_echo_server(
|
||||
ctx: tractor.Context,
|
||||
) -> None:
|
||||
'''
|
||||
Bridge an actor stream to the asyncio echo server.
|
||||
|
||||
'''
|
||||
# this will block until the ``asyncio`` task sends a "first"
|
||||
# message.
|
||||
chan: tractor.to_asyncio.LinkedTaskChannel
|
||||
first: str
|
||||
async with tractor.to_asyncio.open_channel_from(
|
||||
aio_echo_server,
|
||||
) as (chan, first):
|
||||
|
|
@ -38,39 +47,49 @@ async def trio_to_aio_echo_server(
|
|||
assert first == 'start'
|
||||
await ctx.started(first)
|
||||
|
||||
stream: tractor.MsgStream
|
||||
async with ctx.open_stream() as stream:
|
||||
|
||||
msg: int
|
||||
async for msg in stream:
|
||||
await chan.send(msg)
|
||||
|
||||
out = await chan.receive()
|
||||
out: int = await chan.receive()
|
||||
# echo back to parent actor-task
|
||||
await stream.send(out)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
'''
|
||||
Run the infected asyncio echo-server example.
|
||||
|
||||
'''
|
||||
an: tractor.ActorNursery
|
||||
async with tractor.open_nursery() as an:
|
||||
p: tractor.Portal = await an.start_actor(
|
||||
portal: tractor.Portal = await an.start_actor(
|
||||
'aio_server',
|
||||
enable_modules=[__name__],
|
||||
infect_asyncio=True,
|
||||
)
|
||||
async with p.open_context(
|
||||
ctx: tractor.Context
|
||||
first: str
|
||||
async with portal.open_context(
|
||||
trio_to_aio_echo_server,
|
||||
) as (ctx, first):
|
||||
|
||||
assert first == 'start'
|
||||
|
||||
count = 0
|
||||
count: int = 0
|
||||
stream: tractor.MsgStream
|
||||
async with ctx.open_stream() as stream:
|
||||
|
||||
delays = []
|
||||
send = time.time()
|
||||
delays: list[float] = []
|
||||
send: float = time.time()
|
||||
|
||||
await stream.send(count)
|
||||
msg: int
|
||||
async for msg in stream:
|
||||
recv = time.time()
|
||||
recv: float = time.time()
|
||||
delays.append(recv - send)
|
||||
assert msg == count
|
||||
count += 1
|
||||
|
|
@ -81,7 +100,7 @@ async def main() -> None:
|
|||
break
|
||||
|
||||
print(f'mean round trip rate (Hz): {1/mean(delays)}')
|
||||
await p.cancel_actor()
|
||||
await portal.cancel_actor()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
"""
|
||||
'''
|
||||
Integration test: spawning tractor actors from an MPI process.
|
||||
|
||||
When a parent is launched via ``mpirun``, Open MPI sets ``OMPI_*`` env
|
||||
vars that bind ``MPI_Init`` to the ``orted`` daemon. Tractor children
|
||||
inherit those env vars, so if ``inherit_parent_main=True`` (the default)
|
||||
When a parent is launched via ``mpirun``, Open MPI sets
|
||||
``OMPI_*`` env vars that bind ``MPI_Init`` to the ``orted``
|
||||
daemon. Tractor children inherit those env vars, so if
|
||||
``inherit_parent_main=True`` (the default)
|
||||
the child re-executes ``__main__``, re-imports ``mpi4py``, and
|
||||
``MPI_Init_thread`` fails because the child was never spawned by
|
||||
``orted``::
|
||||
|
|
@ -12,13 +13,15 @@ the child re-executes ``__main__``, re-imports ``mpi4py``, and
|
|||
--> Returned value No permission (-17) instead of ORTE_SUCCESS
|
||||
|
||||
Passing ``inherit_parent_main=False`` and placing RPC functions in a
|
||||
separate importable module (``_child``) avoids the re-import entirely.
|
||||
separate importable module (``_child``) avoids the re-import
|
||||
entirely.
|
||||
|
||||
Usage::
|
||||
|
||||
mpirun --allow-run-as-root -np 1 python -m \
|
||||
examples.integration.mpi4py.inherit_parent_main
|
||||
"""
|
||||
|
||||
'''
|
||||
|
||||
from mpi4py import MPI
|
||||
|
||||
|
|
@ -30,8 +33,13 @@ from ._child import child_fn
|
|||
|
||||
|
||||
async def main() -> None:
|
||||
rank = MPI.COMM_WORLD.Get_rank()
|
||||
print(f"[parent] rank={rank} pid={os.getpid()}", flush=True)
|
||||
'''
|
||||
Spawn an MPI-safe child without replaying the parent main.
|
||||
|
||||
'''
|
||||
rank: int = MPI.COMM_WORLD.Get_rank()
|
||||
pid: int = os.getpid()
|
||||
print(f'[parent] rank={rank} pid={pid}', flush=True)
|
||||
|
||||
an: tractor.ActorNursery
|
||||
async with tractor.open_nursery(start_method='trio') as an:
|
||||
|
|
@ -43,9 +51,9 @@ async def main() -> None:
|
|||
inherit_parent_main=False,
|
||||
)
|
||||
result: str = await portal.run(child_fn)
|
||||
print(f"[parent] got: {result}", flush=True)
|
||||
print(f'[parent] got: {result}', flush=True)
|
||||
await portal.cancel_actor()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
trio.run(main)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from typing import AsyncIterator
|
||||
|
||||
import trio
|
||||
import tractor
|
||||
|
||||
|
|
@ -5,20 +7,30 @@ import tractor
|
|||
log = tractor.log.get_logger('multiportal')
|
||||
|
||||
|
||||
async def stream_data(seed: int = 10):
|
||||
log.info("Starting stream task")
|
||||
async def stream_data(seed: int = 10) -> AsyncIterator[int]:
|
||||
'''
|
||||
Stream a finite sequence of integers.
|
||||
|
||||
'''
|
||||
log.info('Starting stream task')
|
||||
|
||||
i: int
|
||||
for i in range(seed):
|
||||
yield i
|
||||
await trio.sleep(0) # trigger scheduler
|
||||
|
||||
|
||||
async def stream_from_portal(
|
||||
p: tractor.Portal,
|
||||
consumed: list,
|
||||
portal: tractor.Portal,
|
||||
consumed: list[int],
|
||||
) -> None:
|
||||
'''
|
||||
Consume one stream and toggle each value in a shared list.
|
||||
|
||||
async with p.open_stream_from(stream_data) as stream:
|
||||
'''
|
||||
stream: tractor.MsgStream
|
||||
async with portal.open_stream_from(stream_data) as stream:
|
||||
item: int
|
||||
async for item in stream:
|
||||
if item in consumed:
|
||||
consumed.remove(item)
|
||||
|
|
@ -27,24 +39,32 @@ async def stream_from_portal(
|
|||
|
||||
|
||||
async def main() -> None:
|
||||
'''
|
||||
Consume two concurrent streams through one portal.
|
||||
|
||||
'''
|
||||
an: tractor.ActorNursery
|
||||
async with tractor.open_nursery(loglevel='info') as an:
|
||||
|
||||
p: tractor.Portal = await an.start_actor(
|
||||
portal: tractor.Portal = await an.start_actor(
|
||||
'stream_boi',
|
||||
enable_modules=[__name__],
|
||||
)
|
||||
|
||||
consumed: list = []
|
||||
consumed: list[int] = []
|
||||
|
||||
n: trio.Nursery
|
||||
async with trio.open_nursery() as n:
|
||||
for i in range(2):
|
||||
n.start_soon(stream_from_portal, p, consumed)
|
||||
for _ in range(2):
|
||||
n.start_soon(
|
||||
stream_from_portal,
|
||||
portal,
|
||||
consumed,
|
||||
)
|
||||
|
||||
# both streaming consumer tasks have completed and so we should
|
||||
# have nothing in our list thanks to single threadedness
|
||||
# both streaming consumer tasks have completed and so we
|
||||
# should have nothing in our list thanks to single
|
||||
# threadedness
|
||||
assert not consumed
|
||||
|
||||
await an.cancel()
|
||||
|
|
|
|||
|
|
@ -53,15 +53,21 @@ async def fan_out_squares(
|
|||
)
|
||||
# unblock the parent's `.open_context()` entry and
|
||||
# report which leaves came up.
|
||||
await ctx.started(
|
||||
[p.chan.aid.name for p in portals]
|
||||
)
|
||||
leaf_names: list[str] = [
|
||||
portal.chan.aid.name
|
||||
for portal in portals
|
||||
]
|
||||
await ctx.started(leaf_names)
|
||||
squares: dict[int, int] = {}
|
||||
|
||||
async def run_in_leaf(
|
||||
portal: tractor.Portal,
|
||||
x: int,
|
||||
) -> None:
|
||||
'''
|
||||
Run one square calculation in a leaf actor.
|
||||
|
||||
'''
|
||||
squares[x] = await portal.run(
|
||||
compute_square,
|
||||
x=x,
|
||||
|
|
@ -85,12 +91,18 @@ async def fan_out_squares(
|
|||
|
||||
|
||||
async def main() -> None:
|
||||
'''
|
||||
Run the nested actor-tree example.
|
||||
|
||||
'''
|
||||
an: tractor.ActorNursery
|
||||
async with tractor.open_nursery() as an:
|
||||
portal: tractor.Portal = await an.start_actor(
|
||||
'supervisor',
|
||||
enable_modules=[__name__],
|
||||
)
|
||||
ctx: tractor.Context
|
||||
leaf_names: list[str]
|
||||
async with portal.open_context(
|
||||
fan_out_squares,
|
||||
vals=[1, 2, 3, 4],
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
"""
|
||||
'''
|
||||
Demonstration of the prime number detector example from the
|
||||
``concurrent.futures`` docs:
|
||||
|
||||
https://docs.python.org/3/library/concurrent.futures.html#processpoolexecutor-example
|
||||
https://docs.python.org/3/library/concurrent.futures.html\
|
||||
#processpoolexecutor-example
|
||||
|
||||
This uses no extra threads, fancy semaphores or futures; all we need
|
||||
is ``tractor``'s channels.
|
||||
|
||||
"""
|
||||
'''
|
||||
from contextlib import (
|
||||
asynccontextmanager as acm,
|
||||
aclosing,
|
||||
)
|
||||
from typing import Callable
|
||||
from typing import (
|
||||
AsyncIterator,
|
||||
Awaitable,
|
||||
Callable,
|
||||
)
|
||||
import itertools
|
||||
import math
|
||||
import time
|
||||
|
|
@ -21,7 +26,12 @@ import tractor
|
|||
import trio
|
||||
|
||||
|
||||
PRIMES = [
|
||||
type ActorMap = Callable[
|
||||
[Callable[[int], Awaitable[bool]], list[int]],
|
||||
AsyncIterator[tuple[int, bool]],
|
||||
]
|
||||
|
||||
PRIMES: list[int] = [
|
||||
112272535095293,
|
||||
112582705942171,
|
||||
112272535095293,
|
||||
|
|
@ -32,6 +42,10 @@ PRIMES = [
|
|||
|
||||
|
||||
async def is_prime(n: int) -> bool:
|
||||
'''
|
||||
Return whether ``n`` is prime.
|
||||
|
||||
'''
|
||||
if n < 2:
|
||||
return False
|
||||
if n == 2:
|
||||
|
|
@ -47,23 +61,32 @@ async def is_prime(n: int) -> bool:
|
|||
|
||||
|
||||
@acm
|
||||
async def worker_pool(workers: int = 4):
|
||||
"""Though it's a trivial special case for ``tractor``, the well
|
||||
async def worker_pool(
|
||||
workers: int = 4,
|
||||
) -> AsyncIterator[ActorMap]:
|
||||
'''
|
||||
Though it's a trivial special case for ``tractor``, the well
|
||||
known "worker pool" seems to be the defacto "but, I want this
|
||||
process pattern!" for most parallelism pilgrims.
|
||||
|
||||
Yes, the workers stay alive (and ready for work) until you close
|
||||
the context.
|
||||
"""
|
||||
|
||||
'''
|
||||
an: tractor.ActorNursery
|
||||
async with tractor.open_nursery() as an:
|
||||
|
||||
portals: list[tractor.Portal] = []
|
||||
snd_chan: trio.MemorySendChannel[tuple[int, bool]]
|
||||
recv_chan: trio.MemoryReceiveChannel[tuple[int, bool]]
|
||||
snd_chan, recv_chan = trio.open_memory_channel(len(PRIMES))
|
||||
|
||||
i: int
|
||||
for i in range(workers):
|
||||
|
||||
# this starts a new sub-actor (process + trio runtime) and
|
||||
# stores it's "portal" for later use to "submit jobs" (ugh).
|
||||
# this starts a new sub-actor (process + trio
|
||||
# runtime) and stores it's "portal" for later use to
|
||||
# "submit jobs" (ugh).
|
||||
portals.append(
|
||||
await an.start_actor(
|
||||
f'worker_{i}',
|
||||
|
|
@ -72,22 +95,36 @@ async def worker_pool(workers: int = 4):
|
|||
)
|
||||
|
||||
async def _map(
|
||||
worker_func: Callable[[int], bool],
|
||||
sequence: list[int]
|
||||
) -> list[bool]:
|
||||
worker_func: Callable[[int], Awaitable[bool]],
|
||||
sequence: list[int],
|
||||
) -> AsyncIterator[tuple[int, bool]]:
|
||||
'''
|
||||
Dispatch values across workers and yield their results.
|
||||
|
||||
# define an async (local) task to collect results from workers
|
||||
'''
|
||||
# define an async (local) task to collect results from
|
||||
# workers
|
||||
async def send_result(
|
||||
func: Callable,
|
||||
func: Callable[[int], Awaitable[bool]],
|
||||
value: int,
|
||||
portal: tractor.Portal,
|
||||
):
|
||||
await snd_chan.send((value, await portal.run(func, n=value)))
|
||||
) -> None:
|
||||
'''
|
||||
Run one remote worker call and send its result.
|
||||
|
||||
'''
|
||||
result: bool = await portal.run(func, n=value)
|
||||
await snd_chan.send((value, result))
|
||||
|
||||
tn: trio.Nursery
|
||||
async with trio.open_nursery() as tn:
|
||||
|
||||
for value, portal in zip(sequence, itertools.cycle(portals)):
|
||||
value: int
|
||||
portal: tractor.Portal
|
||||
for value, portal in zip(
|
||||
sequence,
|
||||
itertools.cycle(portals),
|
||||
):
|
||||
tn.start_soon(
|
||||
send_result,
|
||||
worker_func,
|
||||
|
|
@ -107,20 +144,29 @@ async def worker_pool(workers: int = 4):
|
|||
|
||||
|
||||
async def main() -> None:
|
||||
'''
|
||||
Report primality results from a pool of actors.
|
||||
|
||||
'''
|
||||
actor_map: ActorMap
|
||||
async with worker_pool() as actor_map:
|
||||
|
||||
start = time.time()
|
||||
start: float = time.time()
|
||||
|
||||
results: AsyncIterator[tuple[int, bool]]
|
||||
async with aclosing(actor_map(is_prime, PRIMES)) as results:
|
||||
number: int
|
||||
prime: bool
|
||||
async for number, prime in results:
|
||||
|
||||
print(f'{number} is prime: {prime}')
|
||||
|
||||
print(f'processing took {time.time() - start} seconds')
|
||||
elapsed: float = time.time() - start
|
||||
print(f'processing took {elapsed} seconds')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
start = time.time()
|
||||
start: float = time.time()
|
||||
trio.run(main)
|
||||
print(f'script took {time.time() - start} seconds')
|
||||
elapsed: float = time.time() - start
|
||||
print(f'script took {elapsed} seconds')
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
"""
|
||||
'''
|
||||
Run with a process monitor from a terminal using::
|
||||
|
||||
$TERM -e watch -n 0.1 "pstree -a $$" \
|
||||
& python examples/parallelism/single_func.py \
|
||||
&& kill $!
|
||||
|
||||
"""
|
||||
'''
|
||||
import os
|
||||
|
||||
import tractor
|
||||
|
|
@ -13,18 +13,24 @@ import trio
|
|||
|
||||
|
||||
async def burn_cpu() -> int:
|
||||
'''
|
||||
Burn CPU briefly and return the current process ID.
|
||||
|
||||
pid = os.getpid()
|
||||
'''
|
||||
pid: int = os.getpid()
|
||||
|
||||
# burn a core @ ~ 50kHz
|
||||
for _ in range(50000):
|
||||
await trio.sleep(1/50000/50)
|
||||
await trio.sleep(1 / 50000 / 50)
|
||||
|
||||
return pid
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
'''
|
||||
Run ``burn_cpu()`` in the parent and a subactor.
|
||||
|
||||
'''
|
||||
async with trio.open_nursery() as tn:
|
||||
|
||||
# burn rubber in the parent too
|
||||
|
|
@ -34,7 +40,7 @@ async def main() -> None:
|
|||
# block on and collect its PID as the caller-side result
|
||||
pid: int = await tractor.to_actor.run(burn_cpu)
|
||||
|
||||
print(f"Collected subproc {pid}")
|
||||
print(f'Collected subproc {pid}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
|
||||
import trio
|
||||
import tractor
|
||||
|
||||
|
||||
async def sleepy_jane() -> None:
|
||||
uid: tuple = tractor.current_actor().uid
|
||||
'''
|
||||
Identify the current actor and sleep forever.
|
||||
|
||||
'''
|
||||
uid: tuple[str, str] = tractor.current_actor().uid
|
||||
print(f'Yo i am actor {uid}')
|
||||
await trio.sleep_forever()
|
||||
|
||||
|
|
@ -28,7 +31,9 @@ async def main() -> None:
|
|||
trio.open_nursery() as tn,
|
||||
):
|
||||
|
||||
for (name, portal) in portal_map.items():
|
||||
name: str
|
||||
portal: tractor.Portal
|
||||
for name, portal in portal_map.items():
|
||||
tn.start_soon(
|
||||
portal.run,
|
||||
sleepy_jane,
|
||||
|
|
|
|||
|
|
@ -3,17 +3,29 @@ import tractor
|
|||
|
||||
|
||||
async def assert_err() -> None:
|
||||
'''
|
||||
Raise an assertion error in the current actor.
|
||||
|
||||
'''
|
||||
assert 0
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
'''
|
||||
Propagate a failing one-shot task from a subactor.
|
||||
|
||||
'''
|
||||
an: tractor.ActorNursery
|
||||
async with tractor.open_nursery() as an:
|
||||
real_actors: list[tractor.Portal] = []
|
||||
i: int
|
||||
for i in range(3):
|
||||
real_actors.append(await an.start_actor(
|
||||
f'actor_{i}',
|
||||
enable_modules=[__name__],
|
||||
))
|
||||
real_actors.append(
|
||||
await an.start_actor(
|
||||
f'actor_{i}',
|
||||
enable_modules=[__name__],
|
||||
)
|
||||
)
|
||||
|
||||
# run one one-shot task actor that will fail immediately;
|
||||
# its error raises right here in the caller's task..
|
||||
|
|
@ -28,4 +40,4 @@ if __name__ == '__main__':
|
|||
# also raises
|
||||
trio.run(main)
|
||||
except tractor.RemoteActorError:
|
||||
print("Look Maa that actor failed hard, hehhh!")
|
||||
print('Look Maa that actor failed hard, hehhh!')
|
||||
|
|
|
|||
|
|
@ -9,16 +9,19 @@ async def simple_rpc(
|
|||
data: int,
|
||||
|
||||
) -> None:
|
||||
'''Test a small ping-pong 2-way streaming server.
|
||||
'''
|
||||
Test a small ping-pong 2-way streaming server.
|
||||
|
||||
'''
|
||||
# signal to parent that we're up much like
|
||||
# ``trio.TaskStatus.started()``
|
||||
await ctx.started(data + 1)
|
||||
|
||||
stream: tractor.MsgStream
|
||||
async with ctx.open_stream() as stream:
|
||||
|
||||
count = 0
|
||||
count: int = 0
|
||||
msg: str
|
||||
async for msg in stream:
|
||||
|
||||
assert msg == 'ping'
|
||||
|
|
@ -30,7 +33,11 @@ async def simple_rpc(
|
|||
|
||||
|
||||
async def main() -> None:
|
||||
'''
|
||||
Exercise bidirectional streaming with a remote actor.
|
||||
|
||||
'''
|
||||
an: tractor.ActorNursery
|
||||
async with tractor.open_nursery() as an:
|
||||
|
||||
portal: tractor.Portal = await an.start_actor(
|
||||
|
|
@ -39,6 +46,9 @@ async def main() -> None:
|
|||
)
|
||||
|
||||
# XXX: syntax requires py3.9
|
||||
ctx: tractor.Context
|
||||
sent: int
|
||||
stream: tractor.MsgStream
|
||||
async with (
|
||||
|
||||
portal.open_context(
|
||||
|
|
@ -52,10 +62,11 @@ async def main() -> None:
|
|||
|
||||
assert sent == 11
|
||||
|
||||
count = 0
|
||||
count: int = 0
|
||||
# receive msgs using async for style
|
||||
await stream.send('ping')
|
||||
|
||||
msg: str
|
||||
async for msg in stream:
|
||||
assert msg == 'pong'
|
||||
await stream.send('ping')
|
||||
|
|
|
|||
|
|
@ -35,11 +35,13 @@ async def client_task() -> None:
|
|||
|
||||
'''
|
||||
# a lookup miss yields `None` (not an error).
|
||||
async with tractor.find_actor('no_such_svc') as portal:
|
||||
assert portal is None
|
||||
maybe_portal: tractor.Portal|None
|
||||
async with tractor.find_actor('no_such_svc') as maybe_portal:
|
||||
assert maybe_portal is None
|
||||
print('client: "no_such_svc" is not registered')
|
||||
# block until the service shows up in the registry,
|
||||
# then call into it through the delivered portal.
|
||||
portal: tractor.Portal
|
||||
async with tractor.wait_for_actor('quote_svc') as portal:
|
||||
quote: float = await portal.run(
|
||||
get_quote,
|
||||
|
|
@ -49,6 +51,10 @@ async def client_task() -> None:
|
|||
|
||||
|
||||
async def main() -> None:
|
||||
'''
|
||||
Run a discoverable quote service and its client.
|
||||
|
||||
'''
|
||||
an: tractor.ActorNursery
|
||||
async with tractor.open_nursery() as an:
|
||||
portal: tractor.Portal = await an.start_actor(
|
||||
|
|
|
|||
|
|
@ -1,22 +1,28 @@
|
|||
import trio
|
||||
import tractor
|
||||
|
||||
tractor.log.get_console_log("INFO")
|
||||
tractor.log.get_console_log('INFO')
|
||||
|
||||
|
||||
async def main(service_name: str) -> None:
|
||||
'''
|
||||
Discover one actor and inspect its registrar connection.
|
||||
|
||||
'''
|
||||
an: tractor.ActorNursery
|
||||
async with tractor.open_nursery() as an:
|
||||
await an.start_actor(service_name)
|
||||
|
||||
portal: tractor.Portal
|
||||
async with tractor.get_registry() as portal:
|
||||
print(f"Registrar is listening on {portal.channel}")
|
||||
async with tractor.get_registry() as reg_portal:
|
||||
print(
|
||||
f'Registrar is listening on {reg_portal.channel}'
|
||||
)
|
||||
|
||||
sockaddr: tractor.Portal
|
||||
async with tractor.wait_for_actor(service_name) as sockaddr:
|
||||
print(f"my_service is found at {sockaddr}")
|
||||
actor_portal: tractor.Portal
|
||||
async with tractor.wait_for_actor(
|
||||
service_name,
|
||||
) as actor_portal:
|
||||
print(f'my_service is found at {actor_portal}')
|
||||
|
||||
await an.cancel()
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ async def tick_stream(
|
|||
# wait for the go-signal ensuring every parent-side
|
||||
# subscriber is attached before any tick is sent.
|
||||
assert await stream.receive() == 'go'
|
||||
i: int
|
||||
for i in range(count):
|
||||
await stream.send(i)
|
||||
# falling out gracefully closes our stream side;
|
||||
|
|
@ -37,15 +38,17 @@ async def tick_stream(
|
|||
async def consume(
|
||||
name: str,
|
||||
stream: tractor.MsgStream,
|
||||
task_status: trio.TaskStatus = trio.TASK_STATUS_IGNORED,
|
||||
task_status: trio.TaskStatus[None] = trio.TASK_STATUS_IGNORED,
|
||||
) -> None:
|
||||
'''
|
||||
Consume a private broadcast-copy of the IPC stream.
|
||||
|
||||
'''
|
||||
bcaster: tractor.trionics.BroadcastReceiver
|
||||
async with stream.subscribe() as bcaster:
|
||||
task_status.started()
|
||||
ticks: list[int] = []
|
||||
tick: int
|
||||
async for tick in bcaster:
|
||||
print(f'{name}: rx {tick}')
|
||||
ticks.append(tick)
|
||||
|
|
@ -54,12 +57,19 @@ async def consume(
|
|||
|
||||
|
||||
async def main() -> None:
|
||||
'''
|
||||
Fan one remote stream out to local subscribers.
|
||||
|
||||
'''
|
||||
an: tractor.ActorNursery
|
||||
async with tractor.open_nursery() as an:
|
||||
portal: tractor.Portal = await an.start_actor(
|
||||
'ticker',
|
||||
enable_modules=[__name__],
|
||||
)
|
||||
ctx: tractor.Context
|
||||
first: int
|
||||
stream: tractor.MsgStream
|
||||
async with (
|
||||
portal.open_context(
|
||||
tick_stream,
|
||||
|
|
@ -72,6 +82,7 @@ async def main() -> None:
|
|||
async with trio.open_nursery() as tn:
|
||||
# use `.start()` so each consumer is known
|
||||
# to be subscribed before the ticks flow.
|
||||
i: int
|
||||
for i in range(3):
|
||||
await tn.start(
|
||||
consume,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from collections.abc import AsyncIterator
|
||||
from contextlib import (
|
||||
asynccontextmanager as acm,
|
||||
)
|
||||
|
|
@ -16,7 +17,11 @@ _lock: trio.Lock|None = None
|
|||
|
||||
@acm
|
||||
async def acquire_singleton_lock(
|
||||
) -> None:
|
||||
) -> AsyncIterator[trio.Lock]:
|
||||
'''
|
||||
Acquire and yield the process-wide lock.
|
||||
|
||||
'''
|
||||
global _lock
|
||||
if _lock is None:
|
||||
log.info('Allocating LOCK')
|
||||
|
|
@ -32,8 +37,15 @@ async def acquire_singleton_lock(
|
|||
|
||||
|
||||
async def hold_lock_forever(
|
||||
task_status: trio.TaskStatus = trio.TASK_STATUS_IGNORED,
|
||||
task_status: trio.TaskStatus[
|
||||
trio.Lock,
|
||||
] = trio.TASK_STATUS_IGNORED,
|
||||
) -> None:
|
||||
'''
|
||||
Hold the singleton lock until cancellation.
|
||||
|
||||
'''
|
||||
lock: trio.Lock
|
||||
async with (
|
||||
tractor.trionics.maybe_raise_from_masking_exc(),
|
||||
acquire_singleton_lock() as lock,
|
||||
|
|
@ -47,6 +59,11 @@ async def main(
|
|||
loglevel: str = 'info',
|
||||
debug_mode: bool = True,
|
||||
) -> None:
|
||||
'''
|
||||
Exercise lock acquisition while cancellation is masked.
|
||||
|
||||
'''
|
||||
tn: trio.Nursery
|
||||
async with (
|
||||
trio.open_nursery() as tn,
|
||||
|
||||
|
|
@ -58,7 +75,7 @@ async def main(
|
|||
from tractor.trionics import _taskc
|
||||
_taskc._mask_cases.clear()
|
||||
|
||||
_lock = await tn.start(
|
||||
_held_lock: trio.Lock = await tn.start(
|
||||
hold_lock_forever,
|
||||
)
|
||||
with trio.move_on_after(0.2):
|
||||
|
|
@ -74,8 +91,8 @@ if __name__ == '__main__':
|
|||
tractor.log.get_console_log(level='info')
|
||||
for case in [True, False]:
|
||||
log.info(
|
||||
f'\n'
|
||||
f'------ RUNNING SCRIPT TRIAL ------\n'
|
||||
'\n'
|
||||
'------ RUNNING SCRIPT TRIAL ------\n'
|
||||
f'ignore_special_cases: {case!r}\n'
|
||||
)
|
||||
trio.run(partial(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from collections.abc import Iterator
|
||||
from contextlib import (
|
||||
contextmanager as cm,
|
||||
# TODO, any diff in async case(s)??
|
||||
|
|
@ -17,7 +18,7 @@ log = tractor.log.get_logger(
|
|||
@cm
|
||||
def teardown_on_exc(
|
||||
raise_from_handler: bool = False,
|
||||
):
|
||||
) -> Iterator[None]:
|
||||
'''
|
||||
You could also have a teardown handler which catches any exc and
|
||||
does some required teardown. In this case the problem is
|
||||
|
|
@ -30,7 +31,7 @@ def teardown_on_exc(
|
|||
except BaseException as _berr:
|
||||
berr = _berr
|
||||
log.exception(
|
||||
f'Handling termination teardown in child due to,\n'
|
||||
'Handling termination teardown in child due to,\n'
|
||||
f'{berr!r}\n'
|
||||
)
|
||||
if raise_from_handler:
|
||||
|
|
@ -54,14 +55,18 @@ def teardown_on_exc(
|
|||
|
||||
|
||||
async def finite_stream_to_rent(
|
||||
tx: trio.abc.SendChannel,
|
||||
tx: trio.abc.SendChannel[int],
|
||||
child_errors_mid_stream: bool,
|
||||
raise_unmasked: bool,
|
||||
|
||||
task_status: trio.TaskStatus[
|
||||
trio.CancelScope,
|
||||
trio.CancelScope|None,
|
||||
] = trio.TASK_STATUS_IGNORED,
|
||||
):
|
||||
) -> None:
|
||||
'''
|
||||
Stream values while reproducing exception masking on close.
|
||||
|
||||
'''
|
||||
async with (
|
||||
# XXX without this unmasker the mid-streaming RTE is never
|
||||
# reported since it is masked by the `tx.aclose()`
|
||||
|
|
@ -135,18 +140,25 @@ async def main(
|
|||
raise_unmasked: bool = False,
|
||||
loglevel: str = 'info',
|
||||
) -> None:
|
||||
'''
|
||||
Reproduce cancellation masking a child-stream failure.
|
||||
|
||||
'''
|
||||
tractor.log.get_console_log(level=loglevel)
|
||||
|
||||
# the `.aclose()` being checkpoints on these
|
||||
# is the source of the problem..
|
||||
tx: trio.MemorySendChannel[int]
|
||||
rx: trio.MemoryReceiveChannel[int]
|
||||
tx, rx = trio.open_memory_channel(1)
|
||||
|
||||
tn: trio.Nursery
|
||||
async with (
|
||||
tractor.trionics.collapse_eg(),
|
||||
trio.open_nursery() as tn,
|
||||
rx as rx,
|
||||
):
|
||||
_child_cs = await tn.start(
|
||||
_child_cs: trio.CancelScope|None = await tn.start(
|
||||
partial(
|
||||
finite_stream_to_rent,
|
||||
child_errors_mid_stream=child_errors_mid_stream,
|
||||
|
|
@ -154,6 +166,7 @@ async def main(
|
|||
tx=tx,
|
||||
)
|
||||
)
|
||||
msg: int
|
||||
async for msg in rx:
|
||||
log.debug(
|
||||
f'Rent rx {msg!r}\n'
|
||||
|
|
@ -162,12 +175,13 @@ async def main(
|
|||
# simulate some external cancellation
|
||||
# request **JUST BEFORE** the child errors.
|
||||
if msg == 65:
|
||||
log.cancel(
|
||||
f'Cancelling parent on,\n'
|
||||
f'msg={msg}\n'
|
||||
f'\n'
|
||||
f'Simulates OOB cancel request!\n'
|
||||
)
|
||||
cancel_msg: str = (
|
||||
'Cancelling parent on,\n'
|
||||
'msg={msg}\n'
|
||||
'\n'
|
||||
'Simulates OOB cancel request!\n'
|
||||
).format(msg=msg)
|
||||
log.cancel(cancel_msg)
|
||||
tn.cancel_scope.cancel()
|
||||
|
||||
|
||||
|
|
@ -176,8 +190,8 @@ if __name__ == '__main__':
|
|||
tractor.log.get_console_log(level='info')
|
||||
for case in [True, False]:
|
||||
log.info(
|
||||
f'\n'
|
||||
f'------ RUNNING SCRIPT TRIAL ------\n'
|
||||
'\n'
|
||||
'------ RUNNING SCRIPT TRIAL ------\n'
|
||||
f'child_errors_midstream: {case!r}\n'
|
||||
)
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -46,7 +46,9 @@ async def point_doubler(
|
|||
# now do it right; the parent receives this as the 2nd
|
||||
# element of its `.open_context()` entry tuple.
|
||||
await ctx.started(Point(x=0, y=0))
|
||||
stream: tractor.MsgStream
|
||||
async with ctx.open_stream() as stream:
|
||||
pt: Point
|
||||
async for pt in stream:
|
||||
# natively decoded to our struct type!
|
||||
assert type(pt) is Point
|
||||
|
|
@ -59,12 +61,19 @@ async def point_doubler(
|
|||
|
||||
|
||||
async def main() -> None:
|
||||
'''
|
||||
Exchange typed ``Point`` payloads with a subactor.
|
||||
|
||||
'''
|
||||
an: tractor.ActorNursery
|
||||
async with tractor.open_nursery() as an:
|
||||
portal: tractor.Portal = await an.start_actor(
|
||||
'point_doubler',
|
||||
enable_modules=[__name__],
|
||||
)
|
||||
ctx: tractor.Context
|
||||
first: Point
|
||||
stream: tractor.MsgStream
|
||||
async with (
|
||||
portal.open_context(
|
||||
point_doubler,
|
||||
|
|
@ -73,6 +82,7 @@ async def main() -> None:
|
|||
):
|
||||
# the (validated) started-value from the child
|
||||
assert first == Point(x=0, y=0)
|
||||
i: int
|
||||
for i in range(3):
|
||||
await stream.send(Point(x=i, y=i))
|
||||
doubled: Point = await stream.receive()
|
||||
|
|
|
|||
|
|
@ -21,13 +21,17 @@ async def report_addr() -> str:
|
|||
Return this actor's own accept (bind) addr + pid.
|
||||
|
||||
'''
|
||||
actor = tractor.current_actor()
|
||||
addr: tuple = actor.accept_addr
|
||||
actor: tractor.Actor = tractor.current_actor()
|
||||
addr: tuple[str, str] = actor.accept_addr
|
||||
pid: int = os.getpid()
|
||||
return f'{actor.name}@{addr} pid={pid}'
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
'''
|
||||
Run a child actor over the UDS transport.
|
||||
|
||||
'''
|
||||
an: tractor.ActorNursery
|
||||
async with tractor.open_nursery(
|
||||
enable_transports=['uds'],
|
||||
|
|
@ -52,7 +56,8 @@ async def main() -> None:
|
|||
)
|
||||
# ask the child for its OWN distinct bind addr: another
|
||||
# socket-file path under the runtime dir.
|
||||
print(f'child says: {await portal.run(report_addr)}')
|
||||
child_report: str = await portal.run(report_addr)
|
||||
print(f'child says: {child_report}')
|
||||
await portal.cancel_actor()
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue