Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
- enh: use the API action `dcor_group_list_for_user` (ckanext-dcor_schemas
1.2.0) for listing the collections of a user, with a fallback for
older servers (#108)
- enh: do not retry API requests when the server name cannot be
resolved (DCOR-Aid froze for minutes on Windows with a wrong server)
- ref: convert `dcoraid.cli` module into a package; the upload task CLI
now lives in `dcoraid.cli.task_upload_task`
- ref: use `super()`, PEP 604 optional type hints, and builtin/
Expand Down
22 changes: 21 additions & 1 deletion dcoraid/api/ckan_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import random
import traceback
import urllib.parse
from urllib3.exceptions import MaxRetryError, NameResolutionError
from urllib3.util.retry import Retry

from dclab.external.packaging import parse as parse_version
Expand All @@ -34,8 +35,27 @@
#: List of supported resource suffixes
SERVER_RSUFFIX: dict[str, list[str]] = {}


class RetryExceptNameResolution(Retry):
"""Retry strategy that gives up immediately if a host cannot be resolved

A failed name lookup is rarely transient (e.g. a typo in the server
name), and on Windows every failed lookup may take half a minute.
"""
def increment(self, method=None, url=None, response=None, error=None,
_pool=None, _stacktrace=None):
if isinstance(error, NameResolutionError):
raise MaxRetryError(_pool, url, error) from error
return super().increment(method=method,
url=url,
response=response,
error=error,
_pool=_pool,
_stacktrace=_stacktrace)


# Configure retry strategy
retry_strategy = Retry(
retry_strategy = RetryExceptNameResolution(
total=3, # Total retries
backoff_factor=1.5, # {backoff factor} * (2 ** ({number prev. retries}))
connect=3, # Handle connection errors (e.g. because the server is busy)
Expand Down
22 changes: 22 additions & 0 deletions tests/test_api_base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import socket
import time
from unittest import mock
import uuid

import pytest
import requests

from dcoraid.api import ckan_api

Expand Down Expand Up @@ -77,3 +80,22 @@ def test_api_hostname(server, api_hostname):
api = ckan_api.CKANAPI(server=server,
check_ckan_version=False)
assert api.hostname == api_hostname


def test_api_no_retry_on_name_resolution_error():
"""Do not retry requests when the host name cannot be resolved"""
host = f"{uuid.uuid4()}.does.not.exist.example.com"
lookups = []

def getaddrinfo(name, *args, **kwargs):
lookups.append(name)
raise socket.gaierror(socket.EAI_NONAME, "Name or service not known")

api = ckan_api.CKANAPI(server=host,
check_ckan_version=False,
caching=False)
with (mock.patch("socket.getaddrinfo", side_effect=getaddrinfo),
pytest.raises(requests.exceptions.ConnectionError,
match="Failed to resolve")):
api.get("status_show")
assert len(lookups) == 1
Loading