forked from goodboy/tractor
				
			More trip WIP stuff working.. kinda
Get a few more things working: - fail reliably when remote module loading goes awry - do a real hacky job of module loading using `sys.path` stuffsies - we're still totally borked when trying to spin up and quickly cancel a bunch of subactors... It's a small move forward I guess.try_trip^2
							parent
							
								
									1b7cdfe512
								
							
						
					
					
						commit
						afa640dcab
					
				|  | @ -5,10 +5,14 @@ from collections import defaultdict | ||||||
| from functools import partial | from functools import partial | ||||||
| from itertools import chain | from itertools import chain | ||||||
| import importlib | import importlib | ||||||
|  | import importlib.util | ||||||
| import inspect | import inspect | ||||||
| import uuid | import uuid | ||||||
| import typing | import typing | ||||||
| from typing import Dict, List, Tuple, Any, Optional | from typing import Dict, List, Tuple, Any, Optional | ||||||
|  | from types import ModuleType | ||||||
|  | import sys | ||||||
|  | import os | ||||||
| 
 | 
 | ||||||
| import trio  # type: ignore | import trio  # type: ignore | ||||||
| from trio_typing import TaskStatus | from trio_typing import TaskStatus | ||||||
|  | @ -165,7 +169,7 @@ class Actor: | ||||||
|     def __init__( |     def __init__( | ||||||
|         self, |         self, | ||||||
|         name: str, |         name: str, | ||||||
|         rpc_module_paths: List[str] = [], |         rpc_module_paths: Dict[str, ModuleType] = {}, | ||||||
|         statespace: Optional[Dict[str, Any]] = None, |         statespace: Optional[Dict[str, Any]] = None, | ||||||
|         uid: str = None, |         uid: str = None, | ||||||
|         loglevel: str = None, |         loglevel: str = None, | ||||||
|  | @ -226,9 +230,21 @@ class Actor: | ||||||
|         code (if it exists). |         code (if it exists). | ||||||
|         """ |         """ | ||||||
|         try: |         try: | ||||||
|             for path in self.rpc_module_paths: |             for path, absfilepath in self.rpc_module_paths.items(): | ||||||
|                 log.debug(f"Attempting to import {path}") |                 log.debug(f"Attempting to import {path}") | ||||||
|                 self._mods[path] = importlib.import_module(path) |                 # spec = importlib.util.spec_from_file_location( | ||||||
|  |                 #     path, absfilepath) | ||||||
|  |                 # mod = importlib.util.module_from_spec(spec) | ||||||
|  | 
 | ||||||
|  |                 # XXX append the allowed module to the python path | ||||||
|  |                 # which should allow for relative (at least downward) | ||||||
|  |                 # imports. Seems to be the only that will work currently | ||||||
|  |                 # to get `trio-run-in-process` to import modules we "send | ||||||
|  |                 # it". | ||||||
|  |                 sys.path.append(os.path.dirname(absfilepath)) | ||||||
|  |                 # spec.loader.exec_module(mod) | ||||||
|  |                 mod = importlib.import_module(path) | ||||||
|  |                 self._mods[path] = mod | ||||||
| 
 | 
 | ||||||
|             # if self.name != 'arbiter': |             # if self.name != 'arbiter': | ||||||
|             #     importlib.import_module('doggy') |             #     importlib.import_module('doggy') | ||||||
|  |  | ||||||
|  | @ -5,6 +5,9 @@ Mostly just wrapping around ``multiprocessing``. | ||||||
| """ | """ | ||||||
| import multiprocessing as mp | import multiprocessing as mp | ||||||
| 
 | 
 | ||||||
|  | # from . import log | ||||||
|  | 
 | ||||||
|  | import trio | ||||||
| import trio_run_in_process | import trio_run_in_process | ||||||
| 
 | 
 | ||||||
| try: | try: | ||||||
|  | @ -63,6 +66,7 @@ async def new_proc( | ||||||
|     # passed through to actor main |     # passed through to actor main | ||||||
|     bind_addr: Tuple[str, int], |     bind_addr: Tuple[str, int], | ||||||
|     parent_addr: Tuple[str, int], |     parent_addr: Tuple[str, int], | ||||||
|  |     nursery: trio.Nursery = None, | ||||||
|     use_trip: bool = True, |     use_trip: bool = True, | ||||||
| ) -> mp.Process: | ) -> mp.Process: | ||||||
|     """Create a new ``multiprocessing.Process`` using the |     """Create a new ``multiprocessing.Process`` using the | ||||||
|  | @ -72,8 +76,14 @@ async def new_proc( | ||||||
|         mng = trio_run_in_process.open_in_process( |         mng = trio_run_in_process.open_in_process( | ||||||
|             actor._trip_main, |             actor._trip_main, | ||||||
|             bind_addr, |             bind_addr, | ||||||
|             parent_addr |             parent_addr, | ||||||
|  |             nursery=nursery, | ||||||
|         ) |         ) | ||||||
|  |         # XXX playing with trip logging | ||||||
|  |         # l  = log.get_console_log(level='debug', name=None, _root_name='trio-run-in-process') | ||||||
|  |         # import logging | ||||||
|  |         # logger = logging.getLogger("trio-run-in-process") | ||||||
|  |         # logger.setLevel('DEBUG') | ||||||
|         proc = await mng.__aenter__() |         proc = await mng.__aenter__() | ||||||
|         proc.mng = mng |         proc.mng = mng | ||||||
|         return proc |         return proc | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ | ||||||
| ``trio`` inspired apis and helpers | ``trio`` inspired apis and helpers | ||||||
| """ | """ | ||||||
| import inspect | import inspect | ||||||
|  | import importlib | ||||||
| import platform | import platform | ||||||
| import multiprocessing as mp | import multiprocessing as mp | ||||||
| from typing import Tuple, List, Dict, Optional, Any | from typing import Tuple, List, Dict, Optional, Any | ||||||
|  | @ -32,9 +33,10 @@ log = get_logger('tractor') | ||||||
| class ActorNursery: | class ActorNursery: | ||||||
|     """Spawn scoped subprocess actors. |     """Spawn scoped subprocess actors. | ||||||
|     """ |     """ | ||||||
|     def __init__(self, actor: Actor) -> None: |     def __init__(self, actor: Actor, nursery: trio.Nursery) -> None: | ||||||
|         # self.supervisor = supervisor  # TODO |         # self.supervisor = supervisor  # TODO | ||||||
|         self._actor: Actor = actor |         self._actor: Actor = actor | ||||||
|  |         self._nursery = nursery | ||||||
|         self._children: Dict[ |         self._children: Dict[ | ||||||
|             Tuple[str, str], |             Tuple[str, str], | ||||||
|             Tuple[Actor, mp.Process, Optional[Portal]] |             Tuple[Actor, mp.Process, Optional[Portal]] | ||||||
|  | @ -43,6 +45,7 @@ class ActorNursery: | ||||||
|         # cancelled when their "main" result arrives |         # cancelled when their "main" result arrives | ||||||
|         self._cancel_after_result_on_exit: set = set() |         self._cancel_after_result_on_exit: set = set() | ||||||
|         self.cancelled: bool = False |         self.cancelled: bool = False | ||||||
|  |         # self._aexitstack = contextlib.AsyncExitStack() | ||||||
| 
 | 
 | ||||||
|     async def __aenter__(self): |     async def __aenter__(self): | ||||||
|         return self |         return self | ||||||
|  | @ -56,10 +59,16 @@ class ActorNursery: | ||||||
|         loglevel: str = None,  # set log level per subactor |         loglevel: str = None,  # set log level per subactor | ||||||
|     ) -> Portal: |     ) -> Portal: | ||||||
|         loglevel = loglevel or self._actor.loglevel or get_loglevel() |         loglevel = loglevel or self._actor.loglevel or get_loglevel() | ||||||
|  | 
 | ||||||
|  |         mods = {} | ||||||
|  |         for path in rpc_module_paths or (): | ||||||
|  |             mod = importlib.import_module(path) | ||||||
|  |             mods[path] = mod.__file__ | ||||||
|  | 
 | ||||||
|         actor = Actor( |         actor = Actor( | ||||||
|             name, |             name, | ||||||
|             # modules allowed to invoked funcs from |             # modules allowed to invoked funcs from | ||||||
|             rpc_module_paths=rpc_module_paths or [], |             rpc_module_paths=mods, | ||||||
|             statespace=statespace,  # global proc state vars |             statespace=statespace,  # global proc state vars | ||||||
|             loglevel=loglevel, |             loglevel=loglevel, | ||||||
|             arbiter_addr=current_actor()._arb_addr, |             arbiter_addr=current_actor()._arb_addr, | ||||||
|  | @ -71,6 +80,7 @@ class ActorNursery: | ||||||
|             actor, |             actor, | ||||||
|             bind_addr, |             bind_addr, | ||||||
|             parent_addr, |             parent_addr, | ||||||
|  |             self._nursery, | ||||||
|         ) |         ) | ||||||
|         # `multiprocessing` only (since no async interface): |         # `multiprocessing` only (since no async interface): | ||||||
|         # register the process before start in case we get a cancel |         # register the process before start in case we get a cancel | ||||||
|  | @ -98,7 +108,7 @@ class ActorNursery: | ||||||
|         name: str, |         name: str, | ||||||
|         fn: typing.Callable, |         fn: typing.Callable, | ||||||
|         bind_addr: Tuple[str, int] = ('127.0.0.1', 0), |         bind_addr: Tuple[str, int] = ('127.0.0.1', 0), | ||||||
|         rpc_module_paths: List[str] = [], |         rpc_module_paths: Optional[List[str]] = None, | ||||||
|         statespace: Dict[str, Any] = None, |         statespace: Dict[str, Any] = None, | ||||||
|         loglevel: str = None,  # set log level per subactor |         loglevel: str = None,  # set log level per subactor | ||||||
|         **kwargs,  # explicit args to ``fn`` |         **kwargs,  # explicit args to ``fn`` | ||||||
|  | @ -113,7 +123,7 @@ class ActorNursery: | ||||||
|         mod_path = fn.__module__ |         mod_path = fn.__module__ | ||||||
|         portal = await self.start_actor( |         portal = await self.start_actor( | ||||||
|             name, |             name, | ||||||
|             rpc_module_paths=[mod_path] + rpc_module_paths, |             rpc_module_paths=[mod_path] + (rpc_module_paths or []), | ||||||
|             bind_addr=bind_addr, |             bind_addr=bind_addr, | ||||||
|             statespace=statespace, |             statespace=statespace, | ||||||
|             loglevel=loglevel, |             loglevel=loglevel, | ||||||
|  | @ -195,7 +205,8 @@ class ActorNursery: | ||||||
|         async def wait_for_proc( |         async def wait_for_proc( | ||||||
|             proc: mp.Process, |             proc: mp.Process, | ||||||
|             actor: Actor, |             actor: Actor, | ||||||
|             cancel_scope: Optional[trio.CancelScope] = None, |             portal: Portal, | ||||||
|  |             cancel_scope: Optional[trio._core._run.CancelScope] = None, | ||||||
|         ) -> None: |         ) -> None: | ||||||
|             # please god don't hang |             # please god don't hang | ||||||
|             if not isinstance(proc, trio_run_in_process.process.Process): |             if not isinstance(proc, trio_run_in_process.process.Process): | ||||||
|  | @ -205,7 +216,21 @@ class ActorNursery: | ||||||
|                 proc.join() |                 proc.join() | ||||||
|             else: |             else: | ||||||
|                 # trio_run_in_process blocking wait |                 # trio_run_in_process blocking wait | ||||||
|                 await proc.mng.__aexit__(None, None, None) |                     if errors: | ||||||
|  |                         multierror = trio.MultiError(errors) | ||||||
|  |                         # import pdb; pdb.set_trace() | ||||||
|  |                         # try: | ||||||
|  |                         #     with trio.CancelScope(shield=True): | ||||||
|  |                         #         await proc.mng.__aexit__( | ||||||
|  |                         #             type(multierror), | ||||||
|  |                         #             multierror, | ||||||
|  |                         #             multierror.__traceback__, | ||||||
|  |                         #         ) | ||||||
|  |                         # except BaseException as err: | ||||||
|  |                         #     import pdb; pdb.set_trace() | ||||||
|  |                         #     pass | ||||||
|  |                     # else: | ||||||
|  |                         await proc.mng.__aexit__(None, None, None) | ||||||
|                 # proc.nursery.cancel_scope.cancel() |                 # proc.nursery.cancel_scope.cancel() | ||||||
| 
 | 
 | ||||||
|             log.debug(f"Joined {proc}") |             log.debug(f"Joined {proc}") | ||||||
|  | @ -214,7 +239,7 @@ class ActorNursery: | ||||||
| 
 | 
 | ||||||
|             # proc terminated, cancel result waiter that may have |             # proc terminated, cancel result waiter that may have | ||||||
|             # been spawned in tandem if not done already |             # been spawned in tandem if not done already | ||||||
|             if cancel_scope: |             if cancel_scope: # and not portal._cancelled: | ||||||
|                 log.warning( |                 log.warning( | ||||||
|                     f"Cancelling existing result waiter task for {actor.uid}") |                     f"Cancelling existing result waiter task for {actor.uid}") | ||||||
|                 cancel_scope.cancel() |                 cancel_scope.cancel() | ||||||
|  | @ -226,18 +251,19 @@ class ActorNursery: | ||||||
|         errors: List[Exception] = [] |         errors: List[Exception] = [] | ||||||
|         # wait on run_in_actor() tasks, unblocks when all complete |         # wait on run_in_actor() tasks, unblocks when all complete | ||||||
|         async with trio.open_nursery() as nursery: |         async with trio.open_nursery() as nursery: | ||||||
|  |         # async with self._nursery as nursery: | ||||||
|             for subactor, proc, portal in children.values(): |             for subactor, proc, portal in children.values(): | ||||||
|                 cs = None |                 cs = None | ||||||
|                 # portal from ``run_in_actor()`` |                 # portal from ``run_in_actor()`` | ||||||
|                 if portal in self._cancel_after_result_on_exit: |                 if portal in self._cancel_after_result_on_exit: | ||||||
|                     assert portal |  | ||||||
|                     cs = await nursery.start( |                     cs = await nursery.start( | ||||||
|                         cancel_on_completion, portal, subactor) |                         cancel_on_completion, portal, subactor) | ||||||
|                     # TODO: how do we handle remote host spawned actors? |                     # TODO: how do we handle remote host spawned actors? | ||||||
|                     nursery.start_soon( |                     nursery.start_soon( | ||||||
|                         wait_for_proc, proc, subactor, cs) |                         wait_for_proc, proc, subactor, portal, cs) | ||||||
| 
 | 
 | ||||||
|         if errors: |         if errors: | ||||||
|  |             multierror = trio.MultiError(errors) | ||||||
|             if not self.cancelled: |             if not self.cancelled: | ||||||
|                 # bubble up error(s) here and expect to be called again |                 # bubble up error(s) here and expect to be called again | ||||||
|                 # once the nursery has been cancelled externally (ex. |                 # once the nursery has been cancelled externally (ex. | ||||||
|  | @ -255,8 +281,7 @@ class ActorNursery: | ||||||
|         async with trio.open_nursery() as nursery: |         async with trio.open_nursery() as nursery: | ||||||
|             for subactor, proc, portal in children.values(): |             for subactor, proc, portal in children.values(): | ||||||
|                 # TODO: how do we handle remote host spawned actors? |                 # TODO: how do we handle remote host spawned actors? | ||||||
|                 assert portal |                 nursery.start_soon(wait_for_proc, proc, subactor, portal, cs) | ||||||
|                 nursery.start_soon(wait_for_proc, proc, subactor, cs) |  | ||||||
| 
 | 
 | ||||||
|         log.debug(f"All subactors for {self} have terminated") |         log.debug(f"All subactors for {self} have terminated") | ||||||
|         if errors: |         if errors: | ||||||
|  | @ -364,10 +389,18 @@ class ActorNursery: | ||||||
| async def open_nursery() -> typing.AsyncGenerator[ActorNursery, None]: | async def open_nursery() -> typing.AsyncGenerator[ActorNursery, None]: | ||||||
|     """Create and yield a new ``ActorNursery``. |     """Create and yield a new ``ActorNursery``. | ||||||
|     """ |     """ | ||||||
|  |     # TODO: figure out supervisors from erlang | ||||||
|  | 
 | ||||||
|     actor = current_actor() |     actor = current_actor() | ||||||
|     if not actor: |     if not actor: | ||||||
|         raise RuntimeError("No actor instance has been defined yet?") |         raise RuntimeError("No actor instance has been defined yet?") | ||||||
| 
 | 
 | ||||||
|     # TODO: figure out supervisors from erlang |     # XXX we need this nursery because TRIP is doing all its stuff with | ||||||
|     async with ActorNursery(actor) as nursery: |     # an `@asynccontextmanager` which has an internal nursery *and* the | ||||||
|         yield nursery |     # task that opens a nursery must also close it - so we need a path | ||||||
|  |     # in TRIP to make this all kinda work as well. Note I'm basically | ||||||
|  |     # giving up for now - it's probably equivalent amounts of work to | ||||||
|  |     # make TRIP vs. `multiprocessing` work here. | ||||||
|  |     async with trio.open_nursery() as nursery: | ||||||
|  |         async with ActorNursery(actor, nursery) as anursery: | ||||||
|  |             yield anursery | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue