Skip to content
Open
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
102 changes: 102 additions & 0 deletions src/yocto-patches/forget_unavailable_mirrors.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
diff --git a/bitbake/lib/bb/fetch2/__init__.py b/bitbake/lib/bb/fetch2/__init__.py
index 0ad987c596..c3a81527ec 100644
--- a/bitbake/lib/bb/fetch2/__init__.py
+++ b/bitbake/lib/bb/fetch2/__init__.py
@@ -79,6 +79,19 @@ class FetchError(BBFetchException):
BBFetchException.__init__(self, msg)
self.args = (message, url)

+class Server5xxError(Exception):
+ """exception for 5xx server errors"""
+ def __init__(self, url, message="Server error (5xx)"):
+ self.url = url
+ self.msg = message
+ parsed = urllib.parse.urlparse(url)
+ self.mirror_host = parsed.netloc
+ super().__init__(self.msg)
+
+ def __str__(self):
+ return f"{self.msg} ({self.url})"
+
+
class ChecksumError(FetchError):
"""Exception when mismatched checksum encountered"""
def __init__(self, message, url = None, checksum = None):
@@ -1073,7 +1086,12 @@ def try_mirror_url(fetch, origud, ud, ld, check = False):
try:
if check:
found = ud.method.checkstatus(fetch, ud, ld)
- if found:
+ if found is None:
+ parsed = urllib.parse.urlparse(ud.url)
+ mirror_host = parsed.netloc
+ # Raise errors until we can access the original data and modify the polling loop of mirrors
+ raise Server5xxError(ud.url, f"Server error (5xx) for mirror {mirror_host}")
+ elif found:
return found
return False

@@ -1167,10 +1185,31 @@ def try_mirrors(fetch, d, origud, mirrors, check = False):

uris, uds = build_mirroruris(origud, mirrors, ld)

+ '''
+ TBD: The issue with this loop is that if we don't interrupt it, there will be no effect, and we will iterate through all the addresses from build_mirror_uris.
+ If we abruptly interrupt the loop, the progress of previous polls will be lost. We need to preserve the progress, for example, by restructuring it into a while loop.
+ In this loop, we can iterate and, upon encountering an error, filter out only those addresses that haven't been processed yet.
+ '''
for index, uri in enumerate(uris):
- ret = try_mirror_url(fetch, origud, uds[index], ld, check)
- if ret:
- return ret
+ try:
+ ret = try_mirror_url(fetch, origud, uds[index], ld, check)
+ if ret:
+ return ret
+ except Exception as e:
+ if isinstance(e, Server5xxError):
+ global_d = fetch.d
+ sstate_mirrors = global_d.getVar('SSTATE_MIRRORS') or ""
+ new_mirrors = []
+ '''
+ Add mirror filtering — eliminate those that caused errors (.mirror_host) !!!
+ '''
+ new_value = " ".join(new_mirrors)
+ global_d.setVar('SSTATE_MIRRORS', new_value)
+ logger.warning("Removed mirror %s from SSTATE_MIRRORS due to server error", e.mirror_host)
+ continue
+ else:
+ raise
+
return None

def trusted_network(d, url):
diff --git a/bitbake/lib/bb/fetch2/wget.py b/bitbake/lib/bb/fetch2/wget.py
index 7e43d3bc97..de695ff743 100644
--- a/bitbake/lib/bb/fetch2/wget.py
+++ b/bitbake/lib/bb/fetch2/wget.py
@@ -401,6 +401,24 @@ class Wget(FetchMethod):
with opener.open(r, timeout=100) as response:
pass
except (urllib.error.URLError, ConnectionResetError, TimeoutError) as e:
+
+ errno_code = None
+ if hasattr(e, 'errno') and e.errno:
+ errno_code = e.errno
+ elif hasattr(e, 'reason') and hasattr(e.reason, 'errno') and e.reason.errno:
+ errno_code = e.reason.errno
+ # Server (network) errors
+ server_side_errors = {
+ errno.ECONNREFUSED,
+ errno.ETIMEDOUT,
+ errno.EHOSTUNREACH,
+ errno.ENETUNREACH,
+ errno.ECONNABORTED,
+ errno.ECONNRESET,
+ }
+
+ if errno_code in server_side_errors:
+ return None
if try_again:
logger.debug2("checkstatus: trying again")
return self.checkstatus(fetch, ud, d, False)
49 changes: 49 additions & 0 deletions wiki/patches/forget_unavailable_mirrors_ENG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# forget_unavailable_mirrors.patch

> [!Warning]
> This is a non-working solution -- it is a demonstration of an approach to server filtering.

This patch and the one related to it were sent to Bitbake:

1. https://lists.openembedded.org/g/bitbake-devel/message/17596 -- Explanation of the patch (this is where the main discussion is). The first version of the patch was noticed after I sent an email with the patch described in this file.
2. https://lists.openembedded.org/g/bitbake-devel/message/17760 -- Email with the patch described in this file. There was no response to it. After sending it, I received responses to the previous version indicating that nothing should be modified in this part because it complicates the code, and there are few situations where this problem can be encountered.

Attempt to implement "forgetting" unavailable servers with cache upon receiving an unavailability error (5** code)

> [!IMPORTANT]
> Description for commit e4a79c9a60893e2ec36ea4634ee52a0704185a60

### Main Problem
The main issue is that bitbake lacks such functionality. The entire processing logic is based on either finding a file and returning true or not finding it and returning false.
Integrating this logic is a complex and significant task because the polling method depends on the interaction protocol supported in bitbake. This means that for each protocol, we need to implement the logic for tracking server unavailability errors, return a specific value up the call stack to somehow modify the logic of future polls. The task is complicated by the system's architecture: during the download, starting from a certain point, there is no access to the data class (only to its copy), which prevents removing all mentions of the unavailable server from the method where the unavailability error is encountered.

### Solution
Use raise to ascend to the level where the mirror polling logic is abstractly described and, upon receiving an error, intercept it and modify the future processing logic.
At this stage, this "ascent" to the point where the server polling is initiated—the `try_mirrors` method—has been implemented.

### Current Problem
The current problem is the reorganization of the mirror polling loop.
In the current version of `try_mirrors`, a list of the addresses we are interested in is first obtained:
```
uris, uds = build_mirroruris(origud, mirrors, ld)
```

Then a large polling loop is launched:

```
for index, uri in enumerate(uris):
```

#### Reorganization Problem
1. If we simply update the list of mirrors without changing the loop and uris, there will be no effect; the loop will continue to go through all previously compiled links.
2. If we abruptly terminate the loop, the current progress is lost (polled 3 thousand tasks, encountered an error, interrupted the loop, recalculated uris, started the loop again, and repolled the 3 thousand already polled packages—this is bad).
3. Reorganization while preserving the current progress with dynamic recalculation of uris. It might make sense to rewrite it using a while loop.


### Experiments on the Number of Requests
If the server is unavailable (approximately the same number of requests to a working server), on average:

- 1 request to each package (for .siginfo file)
- 2 requests to each package (for non-.siginfo file)

In total, each non-working server creates a load of 3 * number of tasks. For core-image-minimal, this results in about 6.8 thousand requests.
50 changes: 50 additions & 0 deletions wiki/patches/forget_unavailable_mirrors_RU.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# forget_unavailable_mirrors.patch

> [!Warning]
> Это нерабочее решение -- это демонстрация подхода к фильтрации серверов.

Этот и смежный с ним патч посыслался в Bitbake:
1. https://lists.openembedded.org/g/bitbake-devel/message/17596 -- пояснение к патчу (тут основная дискуссия). Первая версия патча -- на нее обратили внимание после того, как я отправил письмо с патчем, описываемым в данном файле.
2. https://lists.openembedded.org/g/bitbake-devel/message/17760 -- Письмо с патчем, описываемым в данном файле. На него не было ответа. После его отправки -- я получил ответы на предыдущую версию, что не стоит что-то модифицировать в этой части, поскольку это усложняет код, а ситуаций, в которых с данной проблемой можно столкнуться -- мало.


Попытка реализовать "забывание" недоступных серверов с кэшем при получении ошибки недоступности (5** код)

> [!IMPORTANT]
> Описание для коммита e4a79c9a60893e2ec36ea4634ee52a0704185a60

### Основная проблема
Основная проблема в том, что в bitbake такой функциональности нет. Вся логика обработки построена на том, что мы либо находим файл и возвращаем true, либо не находим и возвращаем false.

Интеграция этой логики -- сложная и большая задача, потому что метод опроса зависит от протокола взаимодействия, поддерживаемого в bitbake, т.е. для каждого протокола нужно реализовывать логику отслеживания ошибок недоступности сервера, возвращать специфическое значение наверх по стеку вызовов, чтобы как-то изменить логику будующих опросов. Задача осложняется архитектурой системы: во время скачивания, начиная с некоторого момента, отсутствует доступ к data классу (есть доступ к его копии), что не позволяет из метода, в котором получается ошибка недоступности, убрать все упоминания недоступного сервера.

### Решение
Подыматься с помощью raise на уровень, где абстрактно описана логика опроса зеркал и, в случае получения ошибки, перехватывать ее и менять будущую логику обработки.

На данном этапе реализован этот "подъем" к месту, где запускается опрос серверов -- метод `try_mirrors`.


### Текущая проблема
Текущая проблема -- реорганизация цикла опроса зеркал.
В текущей версии `try_mirrors` сначала получается список интересущих нас адресов:
```
uris, uds = build_mirroruris(origud, mirrors, ld)
```

А потом запускается большой цикл опроса:
```
for index, uri in enumerate(uris):
```

#### Проблема реорганизации
1. Если просто обновить список зеркал, без изменения цикла и `uris`, то эффекта не будет, цикл будет идти по всем заранее составленным ссылкам
2. Если оборвать цикл, то теряется текущий прогресс (опросили 3 тысячи задач, получили ошибку, прервали цикл, пересчитали `uris`, опять запустили цикл и повторно опросили 3 тысячи опрошенных пакетов -- это плохо)
3. Реаорганизация с сохранением текущего прогресса с динамическим пересчетом `uris`. Возможно, имеет смысл переписать на while.


### Эксперименты по количеству запросов
Если сервер недоступен (приблизительно столько же запросов к рабочему серверу), то проходит в среднем:
- 1 запрос к каждому пакету (для файла `.siginfo`)
- 2 запрос к каждому пакету (для файла не `.siginfo`)

Итого каждый нерабочий сервер создает нагрузку 3*количество задач. Для core-image-minimal около 6.8 тысяч запросов получается по итогу.