Skip to content

Commit

Permalink
feat: add p2p daemon (#164)
Browse files Browse the repository at this point in the history
* Add p2p daemon

* Test p2p daemon exits correctly

* Impose restriction on elapsed time

Co-authored-by: Ilya Kobelev <[email protected]>
  • Loading branch information
2 people authored and dvmazur committed Mar 10, 2021
1 parent ee3fb4c commit f3ee6ed
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 0 deletions.
1 change: 1 addition & 0 deletions hivemind/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from hivemind.client import *
from hivemind.dht import *
from hivemind.p2p import *
from hivemind.server import *
from hivemind.utils import *

Expand Down
1 change: 1 addition & 0 deletions hivemind/p2p/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from hivemind.p2p.p2p_daemon import P2P
45 changes: 45 additions & 0 deletions hivemind/p2p/p2p_daemon.py
Original file line number Diff line number Diff line change
@@ -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
55 changes: 55 additions & 0 deletions tests/test_p2p_daemon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import subprocess
from time import perf_counter

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):
start = perf_counter()
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)
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():
with pytest.raises(RuntimeError):
P2P(faulty='argument')

0 comments on commit f3ee6ed

Please sign in to comment.