Skip to content

Commit

Permalink
Add a comment and tests to @catch_db_is_corrupted_exception decorator
Browse files Browse the repository at this point in the history
  • Loading branch information
kozlovsky committed Nov 20, 2023
1 parent a859041 commit 8924ca4
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 3 deletions.
35 changes: 33 additions & 2 deletions src/tribler/core/upgrade/tests/test_upgrader.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import time
from pathlib import Path
from typing import Set
from unittest.mock import patch
from unittest.mock import Mock, patch

import pytest
from ipv8.keyvault.private.libnaclkey import LibNaCLSK
Expand All @@ -15,8 +15,10 @@
from tribler.core.tests.tools.common import TESTS_DATA_DIR
from tribler.core.upgrade.db8_to_db10 import calc_progress
from tribler.core.upgrade.tags_to_knowledge.tags_db import TagDatabase
from tribler.core.upgrade.upgrade import TriblerUpgrader, cleanup_noncompliant_channel_torrents
from tribler.core.upgrade.upgrade import TriblerUpgrader, catch_db_is_corrupted_exception, \
cleanup_noncompliant_channel_torrents
from tribler.core.utilities.configparser import CallbackConfigParser
from tribler.core.utilities.pony_utils import DatabaseIsCorrupted
from tribler.core.utilities.utilities import random_infohash


Expand Down Expand Up @@ -55,6 +57,35 @@ def _copy(source_name, target):
shutil.copyfile(source, target)


def test_catch_db_is_corrupted_exception_with_exception():
upgrader = Mock(_db_is_corrupted_exception=None)
upgrader_method = Mock(side_effect=DatabaseIsCorrupted())
decorated_method = catch_db_is_corrupted_exception(upgrader_method)

# Call the decorated method and expect it to catch the exception
decorated_method(upgrader)
upgrader_method.assert_called_once()

# Check if the exception was caught and stored
upgrader_method.assert_called_once()
assert isinstance(upgrader._db_is_corrupted_exception, DatabaseIsCorrupted)
upgrader._logger.exception.assert_called_once()


def test_catch_db_is_corrupted_exception_without_exception():
upgrader = Mock(_db_is_corrupted_exception=None)
upgrader_method = Mock()
decorated_method = catch_db_is_corrupted_exception(upgrader_method)

# Call the decorated method and expect it to run without exceptions
decorated_method(upgrader)

# Check if the method was called and no exception was stored
upgrader_method.assert_called_once()
assert upgrader._db_is_corrupted_exception is None
upgrader._logger.exception.assert_not_called()


def test_upgrade_pony_db_complete(upgrader, channels_dir, state_dir, trustchain_keypair,
mds_path): # pylint: disable=W0621
"""
Expand Down
18 changes: 17 additions & 1 deletion src/tribler/core/upgrade/upgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,19 @@ def cleanup_noncompliant_channel_torrents(state_dir):


def catch_db_is_corrupted_exception(upgrader_method):
# This decorator applied for TriblerUpgrader methods. It suppresses and remembers the DatabaseIsCorrupted exception.
# As a result, if one upgrade method raises an exception, the following upgrade methods are still executed.

# The reason for this is the following: it is possible that one upgrade methods upgrades a database A, while
# the next upgrade method upgrades a database B. If a corruption detected in the database A, the database B still
# need to be upgraded. So we want to temporarily suppress DatabaseIsCorrupted exception until all upgrades are
# executed.

# If an upgrade found the database to be corrupted, the database is marked as corrupted. Then, the next upgrade
# will rename the corrupted database file (this is handled by the get_db_version call) and immediately return
# because there is no database to upgrade. So, if one upgrade function detects the database corruption, all the
# following upgrade functions for this specific database will skip the actual upgrade. As a result, a new
# database with the current DB version will be created on the Tribler Core start.

@wraps(upgrader_method)
def new_method(*args, **kwargs):
Expand All @@ -81,7 +94,7 @@ def new_method(*args, **kwargs):
self._logger.exception(exc)

if not self._db_is_corrupted_exception:
self._db_is_corrupted_exception = exc
self._db_is_corrupted_exception = exc # Suppress and remember the exception to re-raise it later

return new_method

Expand Down Expand Up @@ -125,6 +138,9 @@ def run(self):
self.upgrade_pony_db_14to15()

if self._db_is_corrupted_exception:
# The current code is executed in the worker's thread. After all upgrade methods are executed,
# we re-raise the delayed exception, and then it is received and handled in the main thread
# by the UpgradeManager.on_worker_finished signal handler.
raise self._db_is_corrupted_exception # pylint: disable=raising-bad-type

def remove_old_logs(self) -> Tuple[List[Path], List[Path]]:
Expand Down

0 comments on commit 8924ca4

Please sign in to comment.