From de942d37cd5d38bd34045feefb241bafc3dc97ab Mon Sep 17 00:00:00 2001 From: Ilya Kobelev Date: Sun, 28 Feb 2021 21:32:20 +0300 Subject: [PATCH 1/3] Add p2p daemon --- hivemind/__init__.py | 1 + hivemind/p2p/__init__.py | 1 + hivemind/p2p/p2p_daemon.py | 45 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 hivemind/p2p/__init__.py create mode 100644 hivemind/p2p/p2p_daemon.py diff --git a/hivemind/__init__.py b/hivemind/__init__.py index 4080b0b9b..8e0d7d144 100644 --- a/hivemind/__init__.py +++ b/hivemind/__init__.py @@ -1,5 +1,6 @@ from hivemind.client import * from hivemind.dht import * +from hivemind.p2p import * from hivemind.server import * from hivemind.utils import * diff --git a/hivemind/p2p/__init__.py b/hivemind/p2p/__init__.py new file mode 100644 index 000000000..6bae0b8bd --- /dev/null +++ b/hivemind/p2p/__init__.py @@ -0,0 +1 @@ +from hivemind.p2p.p2p_daemon import P2P diff --git a/hivemind/p2p/p2p_daemon.py b/hivemind/p2p/p2p_daemon.py new file mode 100644 index 000000000..3083c70e5 --- /dev/null +++ b/hivemind/p2p/p2p_daemon.py @@ -0,0 +1,45 @@ +import subprocess +import typing as tp + + +class P2P(object): + """ + Forks a child process and executes p2pd command with given arguments. + Sends SIGKILL to the child in destructor and on exit from contextmanager. + """ + + LIBP2P_CMD = 'p2pd' + + def __init__(self, *args, **kwargs): + self._child = subprocess.Popen(args=self._make_process_args(args, kwargs)) + try: + stdout, stderr = self._child.communicate(timeout=0.2) + except subprocess.TimeoutExpired: + pass + else: + raise RuntimeError(f'p2p daemon exited with stderr: {stderr}') + + def __enter__(self): + return self._child + + def __exit__(self, exc_type, exc_val, exc_tb): + self._kill_child() + + def __del__(self): + self._kill_child() + + def _kill_child(self): + if self._child.poll() is None: + self._child.kill() + self._child.wait() + + def _make_process_args(self, args: tp.Tuple[tp.Any], + kwargs: tp.Dict[str, tp.Any]) -> tp.List[str]: + proc_args = [self.LIBP2P_CMD] + proc_args.extend( + str(entry) for entry in args + ) + proc_args.extend( + f'-{key}={str(value)}' for key, value in kwargs.items() + ) + return proc_args From 826e223ea813bf81c378834d1ace2bdd5e0dd47f Mon Sep 17 00:00:00 2001 From: Ilya Kobelev Date: Sun, 28 Feb 2021 21:32:51 +0300 Subject: [PATCH 2/3] Test p2p daemon exits correctly --- tests/test_p2p_daemon.py | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/test_p2p_daemon.py diff --git a/tests/test_p2p_daemon.py b/tests/test_p2p_daemon.py new file mode 100644 index 000000000..84ee71a64 --- /dev/null +++ b/tests/test_p2p_daemon.py @@ -0,0 +1,50 @@ +import subprocess + +import pytest + +import hivemind.p2p +from hivemind.p2p import P2P + +RUNNING = 'running' +NOT_RUNNING = 'not running' +CHECK_PID_CMD = ''' +if ps -p {0} > /dev/null; +then + echo "{1}" +else + echo "{2}" +fi +''' + + +def is_process_running(pid: int) -> bool: + cmd = CHECK_PID_CMD.format(pid, RUNNING, NOT_RUNNING) + return subprocess.check_output(cmd, shell=True).decode('utf-8').strip() == RUNNING + + +@pytest.fixture() +def mock_p2p_class(): + P2P.LIBP2P_CMD = "sleep" + + +def test_daemon_killed_on_del(mock_p2p_class): + p2p_daemon = P2P('10s') + + child_pid = p2p_daemon._child.pid + assert is_process_running(child_pid) + + del p2p_daemon + assert not is_process_running(child_pid) + + +def test_daemon_killed_on_exit(mock_p2p_class): + with P2P('10s') as daemon: + child_pid = daemon.pid + assert is_process_running(child_pid) + + assert not is_process_running(child_pid) + + +def test_daemon_raises_on_faulty_args(): + with pytest.raises(RuntimeError): + P2P(faulty='argument') From 5a2e4318da4afc304e721fb79391aa808a76bd32 Mon Sep 17 00:00:00 2001 From: Ilya Kobelev Date: Sun, 28 Feb 2021 21:42:47 +0300 Subject: [PATCH 3/3] Impose restriction on elapsed time --- tests/test_p2p_daemon.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_p2p_daemon.py b/tests/test_p2p_daemon.py index 84ee71a64..ac57e9e2f 100644 --- a/tests/test_p2p_daemon.py +++ b/tests/test_p2p_daemon.py @@ -1,4 +1,5 @@ import subprocess +from time import perf_counter import pytest @@ -28,6 +29,7 @@ def mock_p2p_class(): def test_daemon_killed_on_del(mock_p2p_class): + start = perf_counter() p2p_daemon = P2P('10s') child_pid = p2p_daemon._child.pid @@ -35,14 +37,17 @@ def test_daemon_killed_on_del(mock_p2p_class): del p2p_daemon assert not is_process_running(child_pid) + assert perf_counter() - start < 1 def test_daemon_killed_on_exit(mock_p2p_class): + start = perf_counter() with P2P('10s') as daemon: child_pid = daemon.pid assert is_process_running(child_pid) assert not is_process_running(child_pid) + assert perf_counter() - start < 1 def test_daemon_raises_on_faulty_args():