async def test_Process_object_wait_for_pid(): async def return7(): return 7 async with open_in_process(return7) as proc: await asyncio.wait_for(proc.wait_pid(), timeout=2) assert isinstance(proc.pid, int)
async def test_Process_object_wait_for_returncode(): async def system_exit_123(): raise SystemExit(123) async with open_in_process(system_exit_123) as proc: await asyncio.wait_for(proc.wait_returncode(), timeout=2) assert proc.returncode == 123
async def test_Process_object_wait_for_result_when_error(): async def raise_error(): raise ValueError("child-error") async with open_in_process(raise_error) as proc: with pytest.raises(ValueError, match="child-error"): await asyncio.wait_for(proc.wait_result(), timeout=2)
async def test_Process_object_wait_when_error(): async def raise_error(): raise ValueError("child-error") async with open_in_process(raise_error) as proc: await asyncio.wait_for(proc.wait(), timeout=2) assert isinstance(proc.error, ValueError)
async def run(self) -> None: proc_ctx = open_in_process( self._do_run, self._boot_info, subprocess_kwargs=self.get_subprocess_kwargs(), ) async with proc_ctx as proc: try: await proc.wait() except asyncio.CancelledError as err: logger.debug('Component %s exiting. Sending SIGINT to pid=%d', self, proc.pid) proc.send_signal(signal.SIGINT) try: await asyncio.wait_for(proc.wait(), timeout=2) except asyncio.TimeoutError: logger.debug( 'Component %s running in process pid=%d timed out ' 'during shutdown. Sending SIGTERM and exiting.', self, proc.pid, ) proc.send_signal(signal.SIGTERM) pass finally: raise err
async def test_Process_object_wait_for_return_value(): async def return7(): return 7 async with open_in_process(return7) as proc: await asyncio.wait_for(proc.wait_return_value(), timeout=2) assert proc.return_value == 7
async def _run(boot_info: BootInfo, get_base_db_fn: Callable[[BootInfo], LevelDB], component_manager: AsyncioManager) -> None: logger = logging.getLogger('trinity') start_new_session = True if os.getenv('TRINITY_SINGLE_PROCESS_GROUP') == "1": # This is needed because some of our integration tests rely on all processes being in # a single process group. start_new_session = False async with open_in_process( run_db_manager, boot_info, get_base_db_fn, subprocess_kwargs={'start_new_session': start_new_session}, ) as db_proc: logger.info("Started DB server process (pid=%d)", db_proc.pid) try: wait_for_ipc(boot_info.trinity_config.database_ipc_path) except TimeoutError: logger.error("Timeout waiting for database to start. Exiting...") argparse.ArgumentParser().error( message="Timed out waiting for database start") return None try: await component_manager.run() finally: try: await component_manager.stop() finally: logger.info("Terminating DB process") db_proc.send_signal(signal.SIGINT)
async def test_open_proc_SIGINT_while_running(): async def do_sleep_forever(): while True: await asyncio.sleep(0) async with open_in_process(do_sleep_forever) as proc: proc.send_signal(signal.SIGINT) assert proc.returncode == 2
async def test_open_in_proc_SIGTERM_while_running(): async def do_sleep_forever(): while True: await asyncio.sleep(0) async with open_in_process(do_sleep_forever) as proc: proc.terminate() assert proc.returncode == 15
async def run(self) -> None: proc_ctx = open_in_process( self._do_run, self._boot_info, subprocess_kwargs=self.get_subprocess_kwargs(), ) async with proc_ctx as proc: await proc.wait_result_or_raise()
async def test_open_proc_invalid_function_call(): async def takes_no_args(): pass async with open_in_process(takes_no_args, 1, 2, 3) as proc: pass assert proc.returncode == 1 assert isinstance(proc.error, TypeError)
async def test_Process_object_wait_for_result_when_return_value(): async def return7(): return 7 async with open_in_process(return7) as proc: result = await asyncio.wait_for(proc.wait_result(), timeout=2) assert result == 7 assert proc.error is None
async def test_open_proc_unpickleable_params(touch_path): async def takes_open_file(f): pass with pytest.raises(pickle.PickleError): with open(touch_path, "w") as touch_file: async with open_in_process(takes_open_file, touch_file): # this code block shouldn't get executed assert False
async def test_open_in_proc_SIGKILL_while_running(): async def do_sleep_forever(): while True: await asyncio.sleep(0) async with open_in_process(do_sleep_forever) as proc: await proc.kill() assert proc.returncode == -9 assert isinstance(proc.error, ProcessKilled)
async def test_open_proc_KeyboardInterrupt_while_running(): async def do_sleep_forever(): while True: await asyncio.sleep(0) with pytest.raises(KeyboardInterrupt): async with open_in_process(do_sleep_forever) as proc: raise KeyboardInterrupt assert proc.returncode == 2
async def test_Process_object_state_api(): async def return7(): return 7 async with open_in_process(return7) as proc: assert proc.state.is_on_or_after(State.STARTED) await asyncio.wait_for(proc.wait_for_state(State.FINISHED), timeout=2) assert proc.state is State.FINISHED assert proc.return_value == 7
async def test_open_proc_SIGINT_can_be_handled(): async def do_sleep_forever(): try: while True: await asyncio.sleep(0) except KeyboardInterrupt: return 9999 async with open_in_process(do_sleep_forever) as proc: proc.send_signal(signal.SIGINT) assert proc.returncode == 0 assert proc.result == 9999
async def run(self) -> None: proc_ctx = open_in_process( self._do_run, self._boot_info, subprocess_kwargs=self.get_subprocess_kwargs(), ) try: async with proc_ctx as proc: await proc.wait_result_or_raise() finally: # Right now, when we shutdown trinity, all our components terminate with a 15 # returncode (SIGTERM), but ideally they should terminate with a 2 (SIGINT). See the # comment below for why that is. logger.debug("%s terminated: returncode=%s", self, proc.returncode)
async def run(self) -> None: proc_ctx = open_in_process( self._do_run, subprocess_kwargs=self.get_subprocess_kwargs(), ) try: async with proc_ctx as proc: await proc.wait_result_or_raise() finally: # Right now, when we shutdown trinity, all our components terminate with a 15 # returncode (SIGTERM), but ideally they should terminate with a 2 (SIGINT). See the # comment below for why that is. # Only attempt to log the proc's returncode if we succesfully entered the context # manager above. if 'proc' in locals(): returncode = getattr(proc, 'returncode', 'unset') self.logger.debug("%s terminated: returncode=%s", self, returncode)
async def test_SIGINT_on_method_using_run_in_executor(): # This test exists only to show that one needs to be carefull when using run_in_executor() as # asyncio does not cancel the thread/process it starts, so we need to make sure they return or # else open_in_process() hangs forever. In the code below, this is achieved by setting the # stop_loop event before the method passed to open_in_process() returns. If we don't set that # event, the test hangs forever. async def loop_forever_in_executor(): import threading stop_loop = threading.Event() def thread_loop(): import time while not stop_loop.is_set(): time.sleep(0.01) loop = asyncio.get_event_loop() try: await loop.run_in_executor(None, thread_loop) finally: stop_loop.set() async with open_in_process(loop_forever_in_executor) as proc: proc.send_signal(signal.SIGINT) assert proc.returncode == 2
async def runner(): async with open_in_process(store_received_signals) as proc: child_started.set() await proc.wait_result_or_raise()
async def _do_inner(): async with open_in_process(raise_err) as proc: await proc.wait_result_or_raise()