diff --git a/examples/actors/most_basic_supervisor.py b/examples/actors/most_basic_supervisor.py new file mode 100644 index 0000000..45dad34 --- /dev/null +++ b/examples/actors/most_basic_supervisor.py @@ -0,0 +1,86 @@ +import trio +import tractor + + +class Restart(Exception): + """Restart signal""" + + +async def sleep_then_restart(): + actor = tractor.current_actor() + print(f'{actor.uid} starting up!') + await trio.sleep(0.5) + raise Restart('This is a restart signal') + + +async def signal_restart_whole_actor(): + actor = tractor.current_actor() + print(f'{actor.uid} starting up!') + await trio.sleep(0.5) + return 'restart_me' + + +async def respawn_remote_task(portal): + # start a task in the actor at the other end + # of the provided portal, when it signals a restart, + # restart it.. + + # This is much more efficient then restarting the undlerying + # process over and over since the python interpreter runtime + # stays up and we just submit a new task to run (which + # is just the original one we submitted repeatedly. + while True: + try: + await portal.run(sleep_then_restart) + except tractor.RemoteActorError as error: + if 'Restart' in str(error): + # respawn the actor task + continue + + +async def supervisor(): + + async with tractor.open_nursery() as tn: + + p0 = await tn.start_actor('task_restarter', enable_modules=[__name__]) + + # Yes, you can do this from multiple tasks on one actor + # or mulitple lone tasks in multiple subactors. + # We'll show both. + + async with trio.open_nursery() as n: + # we'll doe the first as a lone task restart in a daemon actor + for i in range(4): + n.start_soon(respawn_remote_task, p0) + + # Open another nursery that will respawn sub-actors + + # spawn a set of subactors that will signal restart + # of the group of processes on each failures + portals = [] + + # start initial subactor set + for i in range(4): + p = await tn.run_in_actor(signal_restart_whole_actor) + portals.append(p) + + # now wait on results and respawn actors + # that request it + while True: + + for p in portals: + result = await p.result() + + if result == 'restart_me': + print(f'restarting {p.channel.uid}') + await p.cancel_actor() + await trio.sleep(0.5) + p = await tn.run_in_actor(signal_restart_whole_actor) + portals.append(p) + + # this will block indefinitely so user must + # cancel with ctrl-c + + +if __name__ == '__main__': + trio.run(supervisor)