Skip to content

Commit

Permalink
Store process UID in the database; use it to determine the parent/chi…
Browse files Browse the repository at this point in the history
…ld process. Stop relying on PIDs when detecting running processes
  • Loading branch information
kozlovsky committed Oct 19, 2023
1 parent 5662d71 commit a1bd858
Show file tree
Hide file tree
Showing 15 changed files with 311 additions and 155 deletions.
1 change: 1 addition & 0 deletions src/run_tribler_headless.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ async def signal_handler(sig):
current_process = TriblerProcess.current_process(ProcessKind.Core)
self.process_manager = ProcessManager(root_state_dir, current_process)
set_global_process_manager(self.process_manager)
current_process.start_updating_thread()

if not self.process_manager.current_process.become_primary():
msg = 'Another Core process is already running'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import psutil
from ipv8.taskmanager import TaskManager

GUI_UID_ENV_KEY = 'TRIBLER_GUI_UID'
GUI_PID_ENV_KEY = 'TRIBLER_GUI_PID'
CHECK_INTERVAL = 10

Expand Down Expand Up @@ -52,6 +53,16 @@ def get_gui_pid() -> Optional[int]:
logger.warning(f'Cannot parse {GUI_PID_ENV_KEY} environment variable: {pid}')
return None

@staticmethod
def get_gui_uid() -> Optional[int]:
uid = os.environ.get(GUI_UID_ENV_KEY, None)
if uid:
try:
return int(uid)
except ValueError:
logger.warning(f'Cannot parse {GUI_UID_ENV_KEY} environment variable: {uid}')
return None

@classmethod
def get_gui_process(cls) -> Optional[psutil.Process]:
pid = cls.get_gui_pid()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import psutil
import pytest

from tribler.core.components.gui_process_watcher.gui_process_watcher import GUI_PID_ENV_KEY, GuiProcessNotRunning, \
from tribler.core.components.gui_process_watcher.gui_process_watcher import GUI_PID_ENV_KEY, GUI_UID_ENV_KEY, \
GuiProcessNotRunning, \
GuiProcessWatcher


Expand Down Expand Up @@ -33,6 +34,19 @@ def test_get_gui_pid(caplog):
assert GuiProcessWatcher.get_gui_pid() == 123


def test_get_gui_uid(caplog):
with patch.dict(os.environ, {GUI_UID_ENV_KEY: ''}):
assert GuiProcessWatcher.get_gui_pid() is None

with patch.dict(os.environ, {GUI_UID_ENV_KEY: 'abc'}):
caplog.clear()
assert GuiProcessWatcher.get_gui_uid() is None
assert caplog.records[-1].message == 'Cannot parse TRIBLER_GUI_UID environment variable: abc'

with patch.dict(os.environ, {GUI_UID_ENV_KEY: '123'}):
assert GuiProcessWatcher.get_gui_uid() == 123


def test_get_gui_process():
# pid is not specified
with patch.dict(os.environ, {GUI_PID_ENV_KEY: ''}):
Expand Down
4 changes: 3 additions & 1 deletion src/tribler/core/start_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,9 @@ def run_tribler_core_session(api_port: Optional[int], api_key: str,
def run_core(api_port: Optional[int], api_key: Optional[str], root_state_dir, parsed_args):
logger.info(f"Running Core in {'gui_test_mode' if parsed_args.gui_test_mode else 'normal mode'}")

gui_uid = GuiProcessWatcher.get_gui_uid()

Check warning on line 189 in src/tribler/core/start_core.py

View check run for this annotation

Codecov / codecov/patch

src/tribler/core/start_core.py#L189

Added line #L189 was not covered by tests
gui_pid = GuiProcessWatcher.get_gui_pid()
current_process = TriblerProcess.current_process(ProcessKind.Core, creator_pid=gui_pid)
current_process = TriblerProcess.current_process(kind=ProcessKind.Core, creator_uid=gui_uid, creator_pid=gui_pid)

Check warning on line 191 in src/tribler/core/start_core.py

View check run for this annotation

Codecov / codecov/patch

src/tribler/core/start_core.py#L191

Added line #L191 was not covered by tests
process_manager = ProcessManager(root_state_dir, current_process)
set_global_process_manager(process_manager)
current_process_is_primary = process_manager.current_process.become_primary()
Expand All @@ -199,6 +200,7 @@ def run_core(api_port: Optional[int], api_key: Optional[str], root_state_dir, pa
logger.warning(msg)
process_manager.sys_exit(1, msg)

current_process.start_updating_thread()

Check warning on line 203 in src/tribler/core/start_core.py

View check run for this annotation

Codecov / codecov/patch

src/tribler/core/start_core.py#L203

Added line #L203 was not covered by tests
version_history = VersionHistory(root_state_dir)
state_dir = version_history.code_version.directory
exit_code = run_tribler_core_session(api_port, api_key, state_dir, gui_test_mode=parsed_args.gui_test_mode)
Expand Down
28 changes: 18 additions & 10 deletions src/tribler/core/utilities/process_manager/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def _unable_to_open_db_file_get_reason(self):

return 'unknown reason'

def primary_process_rowid(self, kind: ProcessKind) -> Optional[int]:
def primary_process_uid(self, kind: ProcessKind) -> Optional[int]:
"""
A helper method to load the current primary process of the specified kind from the database.
Expand All @@ -114,18 +114,26 @@ def primary_process_rowid(self, kind: ProcessKind) -> Optional[int]:
with self.connect() as connection:
cursor = connection.execute(f"""
SELECT {sql_scripts.SELECT_COLUMNS}
FROM processes WHERE kind = ? and "primary" = 1 ORDER BY rowid DESC LIMIT 1
FROM processes WHERE kind = ? and is_primary = 1 ORDER BY rowid
""", [kind.value])
row = cursor.fetchone()
if row is not None:
rows = cursor.fetchall()
primary_processes = []
for row in rows:
process = TriblerProcess.from_row(self, row)
if process.is_running():
return process.rowid
primary_processes.append(process)
else:
# Process is not running anymore; mark it as not primary
process.is_primary = False
process.save()

# Process is not running anymore; mark it as not primary
process.primary = False
process.save()
return None
if not primary_processes:
return None

if len(primary_processes) > 1:
raise RuntimeError(f'Several primary processes found of kind {kind}')

Check warning on line 134 in src/tribler/core/utilities/process_manager/manager.py

View check run for this annotation

Codecov / codecov/patch

src/tribler/core/utilities/process_manager/manager.py#L134

Added line #L134 was not covered by tests

return primary_processes[0].uid

def sys_exit(self, exit_code: Optional[int] = None, error: Optional[str | Exception] = None, replace: bool = False):
"""
Expand All @@ -141,7 +149,7 @@ def sys_exit(self, exit_code: Optional[int] = None, error: Optional[str | Except
sys.exit(exit_code)

@with_retry
def get_last_processes(self, limit=6) -> List[TriblerProcess]:
def get_last_processes(self, limit=10) -> List[TriblerProcess]:
"""
Returns last `limit` processes from the database. They are used during the formatting of the error report.
"""
Expand Down

0 comments on commit a1bd858

Please sign in to comment.