From 27c9760f9647b14a5a72e25b35bfda151f649417 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Sun, 26 Jan 2020 21:13:29 -0500 Subject: [PATCH] Be explicit about the spawning backend default Set `trio-run-in-process` as the default on *nix systems and `multiprocessing`'s spawn method on Windows. Enable overriding the default choice using `tractor._spawn.try_set_start_method()`. Allows for easy runs of the test suite using a user chosen backend. --- tractor/__init__.py | 8 +++----- tractor/_actor.py | 6 ++++++ tractor/_spawn.py | 29 ++++++++++++++++++++--------- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/tractor/__init__.py b/tractor/__init__.py index a677c59..80eaf05 100644 --- a/tractor/__init__.py +++ b/tractor/__init__.py @@ -4,7 +4,6 @@ tractor: An actor model micro-framework built on """ import importlib from functools import partial -import platform from typing import Tuple, Any, Optional import typing @@ -103,16 +102,15 @@ def run( # either the `multiprocessing` start method: # https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods # OR `trio_run_in_process` (the new default). - start_method: str = 'trio_run_in_process', + start_method: Optional[str] = None, **kwargs, ) -> Any: """Run a trio-actor async function in process. This is tractor's main entry and the start point for any async actor. """ - if platform.system() == 'Windows': - start_method = 'spawn' # only one supported for now - _spawn.try_set_start_method(start_method) + if start_method is not None: + _spawn.try_set_start_method(start_method) return trio.run(_main, async_fn, args, kwargs, arbiter_addr, name) diff --git a/tractor/_actor.py b/tractor/_actor.py index d46471d..eb0570d 100644 --- a/tractor/_actor.py +++ b/tractor/_actor.py @@ -537,6 +537,7 @@ class Actor: f"Setting loglevel for {self.uid} to {self.loglevel}") get_console_log(self.loglevel) + assert spawn_ctx log.info( f"Started new {spawn_ctx.current_process()} for {self.uid}") @@ -555,6 +556,11 @@ class Actor: accept_addr: Tuple[str, int], parent_addr: Tuple[str, int] = None ) -> None: + """Entry point for a `trio_run_in_process` subactor. + + Here we don't need to call `trio.run()` since trip does that as + part of its subprocess startup sequence. + """ if self.loglevel is not None: log.info( f"Setting loglevel for {self.uid} to {self.loglevel}") diff --git a/tractor/_spawn.py b/tractor/_spawn.py index f35677c..88cc450 100644 --- a/tractor/_spawn.py +++ b/tractor/_spawn.py @@ -6,7 +6,7 @@ Mostly just wrapping around ``multiprocessing``. import inspect import multiprocessing as mp import platform -from typing import Any, List, Dict +from typing import Any, Dict, Optional import trio from trio_typing import TaskStatus @@ -32,8 +32,13 @@ from ._actor import Actor, ActorFailure log = get_logger('tractor') -_ctx: mp.context.BaseContext = mp.get_context("spawn") # type: ignore -_spawn_method: str = "spawn" +# use trip as our default for now +if platform.system() != 'Windows': + _spawn_method: str = "trio_run_in_process" +else: + _spawn_method = "spawn" + +_ctx: Optional[mp.context.BaseContext] = None if platform.system() == 'Windows': @@ -46,7 +51,7 @@ else: await trio.hazmat.wait_readable(proc.sentinel) -def try_set_start_method(name: str) -> mp.context.BaseContext: +def try_set_start_method(name: str) -> Optional[mp.context.BaseContext]: """Attempt to set the start method for ``multiprocess.Process`` spawning. If the desired method is not supported the sub-interpreter (aka "spawn" @@ -55,15 +60,16 @@ def try_set_start_method(name: str) -> mp.context.BaseContext: global _ctx global _spawn_method - allowed = mp.get_all_start_methods() + methods = mp.get_all_start_methods() + methods.remove('fork') - # no Windows support for trip yet (afaik) + # no Windows support for trip yet if platform.system() != 'Windows': - allowed += ['trio_run_in_process'] + methods += ['trio_run_in_process'] - if name not in allowed: + if name not in methods: raise ValueError( - f"Spawn method {name} is unsupported please choose one of {allowed}" + f"Spawn method `{name}` is invalid please choose one of {methods}" ) elif name == 'fork': @@ -73,6 +79,10 @@ def try_set_start_method(name: str) -> mp.context.BaseContext: elif name == 'forkserver': _forkserver_override.override_stdlib() _ctx = mp.get_context(name) + elif name == 'trio_run_in_process': + _ctx = None + else: + _ctx = mp.get_context(name) _spawn_method = name return _ctx @@ -191,6 +201,7 @@ async def new_proc( # TRIP blocks here until process is complete else: # `multiprocessing` + assert _ctx start_method = _ctx.get_start_method() if start_method == 'forkserver': # XXX do our hackery on the stdlib to avoid multiple