Fix mirrors hang when /status endpoint is down (#4031)

* add explicit _fetched_remote bool

* Attempt 2

* Adds about 15 seconds time-out to fetch_data_from_url with 3 retries (4, 5, 6)
Then fallsback to fully local list

* Feedbacks: 20 -> 30
Do not return early
Add debug
Remove new flag
60 second timeout for reflector

* Clean up install logs by hiding mirror scores behind --verbose
This commit is contained in:
HADEON 2025-12-28 01:10:03 +01:00 committed by GitHub
parent c1eae10e93
commit 79313c4942
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 30 additions and 8 deletions

View File

@ -194,9 +194,16 @@ class Installer:
else:
info(tr('Skipping waiting for automatic time sync (this can cause issues if time is out of sync during installation)'))
info('Waiting for automatic mirror selection (reflector) to complete.')
while self._service_state('reflector') not in ('dead', 'failed', 'exited'):
time.sleep(1)
if not arch_config_handler.args.offline:
info('Waiting for automatic mirror selection (reflector) to complete.')
for _ in range(60):
if self._service_state('reflector') in ('dead', 'failed', 'exited'):
break
time.sleep(1)
else:
warn('Reflector did not complete within 60 seconds, continuing anyway...')
else:
info('Skipped reflector...')
# info('Waiting for pacman-init.service to complete.')
# while self._service_state('pacman-init') not in ('dead', 'failed', 'exited'):

View File

@ -389,6 +389,7 @@ class MirrorListHandler:
) -> None:
self._local_mirrorlist = local_mirrorlist
self._status_mappings: dict[str, list[MirrorStatusEntryV3]] | None = None
self._fetched_remote: bool = False
def _mappings(self) -> dict[str, list[MirrorStatusEntryV3]]:
if self._status_mappings is None:
@ -412,9 +413,12 @@ class MirrorListHandler:
from .args import arch_config_handler
if arch_config_handler.args.offline:
self._fetched_remote = False
self.load_local_mirrors()
else:
if not self.load_remote_mirrors():
self._fetched_remote = self.load_remote_mirrors()
debug(f'load mirrors: {self._fetched_remote}')
if not self._fetched_remote:
self.load_local_mirrors()
def load_remote_mirrors(self) -> bool:
@ -441,7 +445,15 @@ class MirrorListHandler:
def get_status_by_region(self, region: str, speed_sort: bool) -> list[MirrorStatusEntryV3]:
mappings = self._mappings()
region_list = mappings[region]
return sorted(region_list, key=lambda mirror: (mirror.score, mirror.speed))
# Only sort if we have remote mirror data with score/speed info
# Local mirrors lack this data and can be modified manually before-hand
# Or reflector potentially ran already
if self._fetched_remote and speed_sort:
# original return
return sorted(region_list, key=lambda mirror: (mirror.score, mirror.speed))
# just return as-is without sorting?
return region_list
def _parse_remote_mirror_list(self, mirrorlist: str) -> dict[str, list[MirrorStatusEntryV3]]:
mirror_status = MirrorStatusListV3.model_validate_json(mirrorlist)

View File

@ -105,10 +105,13 @@ class MirrorStatusEntryV3(BaseModel):
@model_validator(mode='after')
def debug_output(self) -> 'MirrorStatusEntryV3':
from ..args import arch_config_handler
self._hostname, *port = urllib.parse.urlparse(self.url).netloc.split(':', 1)
self._port = int(port[0]) if port and len(port) >= 1 else None
debug(f'Loaded mirror {self._hostname}' + (f' with current score of {self.score}' if self.score else ''))
if arch_config_handler.args.verbose:
debug(f'Loaded mirror {self._hostname}' + (f' with current score of {self.score}' if self.score else ''))
return self

View File

@ -121,7 +121,7 @@ def enrich_iface_types(interfaces: list[str]) -> dict[str, str]:
return result
def fetch_data_from_url(url: str, params: dict[str, str] | None = None) -> str:
def fetch_data_from_url(url: str, params: dict[str, str] | None = None, timeout: int = 30) -> str:
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
@ -133,7 +133,7 @@ def fetch_data_from_url(url: str, params: dict[str, str] | None = None) -> str:
full_url = url
try:
response = urlopen(full_url, context=ssl_context)
response = urlopen(full_url, context=ssl_context, timeout=timeout)
data = response.read().decode('UTF-8')
return data
except URLError as e: