fix: proper handling of retried removals

This commit is contained in:
gmega 2025-01-22 15:28:34 -03:00
parent 1b6d710d4a
commit aad78b9faa
No known key found for this signature in database
GPG Key ID: 6290D34EAD824B18
3 changed files with 47 additions and 8 deletions

View File

@ -57,8 +57,11 @@ class Node(ABC, Generic[TNetworkHandle, TInitialMetadata]):
pass
@abstractmethod
def remove(self, handle: TNetworkHandle):
def remove(self, handle: TNetworkHandle) -> bool:
"""Removes the file associated with the handle from this node. For seeders, this means the node will stop
seeding it. For leechers, it will stop downloading it. In both cases, the file will be removed from the node's
storage."""
storage.
:return: True if the file exists and was successfully removed, False if the file didn't exit.
"""
pass

View File

@ -9,14 +9,20 @@ from typing import List, Optional, Self, Dict, Any
import pathvalidate
from deluge_client import DelugeRPCClient
from tenacity import retry, wait_exponential, stop_after_attempt
from deluge_client.client import RemoteException
from tenacity import (
retry,
wait_exponential,
stop_after_attempt,
retry_if_not_exception_type,
)
from tenacity.stop import stop_base
from tenacity.wait import wait_base
from torrentool.torrent import Torrent
from urllib3.util import Url
from benchmarks.core.experiments.experiments import ExperimentComponent
from benchmarks.core.network import DownloadHandle
from benchmarks.core.network import DownloadHandle, Node
from benchmarks.core.utils import await_predicate
from benchmarks.deluge.agent.client import DelugeAgentClient
@ -32,7 +38,7 @@ class DelugeMeta:
announce_url: Url
class DelugeNode(ExperimentComponent):
class DelugeNode(Node[Torrent, DelugeMeta], ExperimentComponent):
def __init__(
self,
name: str,
@ -110,7 +116,20 @@ class DelugeNode(ExperimentComponent):
)
def remove(self, handle: Torrent):
self.rpc.core.remove_torrent(handle.info_hash, remove_data=True)
try:
self.rpc.core.remove_torrent(handle.info_hash, remove_data=True)
return True
except RemoteException as ex:
# DelugeRPCClient creates remote exception types dynamically, so there's
# actually no way of testing for them other than this.
exception_type = str(ex.__class__)
if "deluge_client.client.InvalidTorrentError" in exception_type:
# This might happen when we retry a failed delete - maybe we got a bad response back,
# but the node managed to delete it already.
logger.warning(f"Torrent {handle.name} was not found on {self.name}.")
return False
else:
raise ex
def torrent_info(self, name: str) -> List[Dict[bytes, Any]]:
return list(self.rpc.core.get_torrents_status({"name": name}, []).values())
@ -161,7 +180,11 @@ class ResilientCallWrapper:
self.stop_policy = stop_policy
def __call__(self, *args, **kwargs):
@retry(wait=self.wait_policy, stop=self.stop_policy)
@retry(
wait=self.wait_policy,
stop=self.stop_policy,
retry=retry_if_not_exception_type(RemoteException),
)
def _resilient_wrapper():
return self.node(*args, **kwargs)

View File

@ -73,10 +73,23 @@ def test_should_remove_files(deluge_node1: DelugeNode, tracker: Tracker):
)
assert_is_seed(deluge_node1, name="dataset1", size=megabytes(1))
deluge_node1.remove(torrent)
assert deluge_node1.remove(torrent)
assert not deluge_node1.torrent_info(name="dataset1")
@pytest.mark.integration
def test_should_return_false_when_file_does_not_exist(
deluge_node1: DelugeNode, deluge_node2: DelugeNode, tracker: Tracker
):
torrent = deluge_node1.genseed(
size=megabytes(1),
seed=1234,
meta=DelugeMeta(name="dataset1", announce_url=tracker.announce_url),
)
assert not deluge_node2.remove(torrent)
class FlakyClient:
def __init__(self):
self.count = 0