Files
av-parser/docs/proxies.md
T
Leonid Pershin f72f08a7bc Enhance proxy handling and error reporting in collection process
- Introduced a `Via` field in `ParseError` to indicate the proxy address used during requests, improving clarity on error contexts.
- Updated `ProxyPool` to prioritize confirmed live proxies while available, ensuring more reliable proxy selection and reducing connection timeouts.
- Implemented fallback logic to allow the use of unconfirmed proxies when no confirmed ones are available, preventing collection stalls.
- Adjusted logging in `CollectLogEntryViewModel` to include proxy details, enhancing error visibility for users.
- Added unit tests to verify new proxy selection logic and ensure correct behavior under various conditions.

These changes improve the robustness of the proxy management system and enhance the overall user experience by providing clearer error messages and more efficient proxy usage.
2026-08-15 14:33:13 +03:00

72 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Прокси-пул
Читать перед правкой `Core/Proxies/**` и `Infrastructure/Proxies/**`.
## Добавить источник
1. Реализовать `IProxySource` (или `IMutableProxySource`, если список редактируемый).
2. Зарегистрировать в `AddAvParserProxies()`. Порядок регистрации = порядок слияния; свой список идёт
последним, чтобы пользовательский адрес перебивал фидовый.
## Инварианты
- **`ProxyPool` переиспользует существующие `ProxyEntry` по `Endpoint.Key`** — иначе перезагрузка
списка стирала бы статистику, а публичные фиды переиздаются каждые несколько минут.
- **Выдаются только подтверждённо живые, пока такие есть.** Сортировки «живые вперёд» не хватало:
её видит только липкая стратегия, берущая голову списка, а round-robin и взвешенный выбор тянут из
всего набора — то есть из пары тысяч непроверенных адресов, и почти каждый запрос платил полный
connect-таймаут, чтобы это выяснить. Теперь «живых N» на странице и то, куда реально ходит сбор, —
одно и то же число.
- **Фолбэк на всё доступное, когда живых нет.** Непроверенная ≠ мёртвая: на холодном пуле строгий
фильтр не выдал бы ничего и сбор просто встал бы, не попробовав. Ответившая на этом пути прокси
сама себя переводит в живые.
- **Карантин остаётся отдельным механизмом от `Health`.** `Health` — «что видели в последний раз»;
исключать навсегда всё когда-либо упавшее значит потерять прокси после первой осечки. Уже было
багом, ловит `A_failing_proxy_is_quarantined_and_comes_back_later`.
- **`LiveCount` считает только `Alive` и не в карантине.** На нём висит гейт сбора, поэтому
«доступна» (окно истекло) и «живая» намеренно расходятся: гейт не должен открываться от одного лишь
истечения окна.
- **Проба не трогает `SuccessCount`/`FailureCount`** — они про реальные запросы, а свип по паре тысяч
адресов перезаписал бы всё, на чём держится взвешенный выбор. Провал пробы даёт карантин через
`RecordProbe(..., quarantineOnFailure:)`.
- **Лиза без вердикта нейтральна**: отменённая операция — не вина прокси, иначе каждый Cancel
карантинил бы здоровые адреса.
- **Вердикт лизы — про транспорт, а не про ресурс.** 404 и 429 — успех прокси; иначе пул карантинил
бы рабочие адреса с частотой мёртвых ссылок, а на лимит отвечал бы сменой прокси, то есть обходом
лимита.
- **Прогрев обрывается по достижении цели**, а не проходит список до конца: `WarmUpAsync` линкует CTS
и гасит остаток на `MinimumLiveProxies`. Порядок кандидатов — `WarmUpOrder()`, публичный ровно ради
проверки без прогона проб.
- **Остаток дочищает `TopUpAsync` в фоне.** Прогрев по построению оставляет тысячи адресов в
`Unknown` — это читается как «проверка не запускалась», и, что хуже, сбор получает пул из десяти
прокси вместо сотен рабочих. Фоновый проход запускает `ProxyPoolLoader` после прогрева, с
четвертью `ProbeConcurrency`: пользовательский свип — это всплеск, за которым смотрят, а этот идёт
минутами и, возможно, параллельно с закачкой.
- **`TopUpAsync` трогает только `Unknown`.** Перепроверять мёртвых — это и есть свип; перепроверять
живых значит посадить в карантин прокси, которой прямо сейчас пользуется прогон.
- **`TopUpAsync` не делает `_strategy.Reset()`**, в отличие от свипа: сброс уронил бы липкий выбор
посреди сбора, причём ради записей, которых в этом выборе и нет. `Changed` он поднимает раз в
`ChangeInterval` проб — иначе страница молчит несколько минут.
- **Фоновый проход не идёт при `ProxyHealthCheck.Lazy`**: «проверять в момент выдачи» — это ровно
просьба не свипать.
- **`SaveStateAsync` сериализован семафором.** Писателей теперь двое (фоновый проход по завершении и
выход из приложения), а запись идёт через один и тот же временный файл с переименованием.
- **`RestoreState` не выставляет `Health = Alive`** — «работала вчера» живёт в `WasAliveOnLastRun` и
влияет только на порядок прогрева. Иначе пул отрапортует живыми тех, с кем не разговаривал: прогрев
сочтёт цель достигнутой, а гейт сбора откроется по данным недельной давности. Ловит
`A_remembered_proxy_is_not_reported_live_until_it_answers_again`.
- **`RestoreState` не восстанавливает карантин**: окно — стенные часы, между запусками могли пройти
сутки, и перенос сажал бы прокси за давно истёкшее.
- **Сохраняются только `HasEverAnswered`** = `SuccessCount > 0 || IsBelievedAlive` («believed» —
вердикт этой сессии, а без него прошлой). Прогрев обрывается рано, поэтому большинство запомненных
заканчивают сессию непроверенными — строгое `Health == Alive` стёрло бы накопленный список за пару
запусков.
- **`Select` и `Next` зарезервированы для CA1716** — метод стратегии называется `Pick`.
## Статистика на «Обзоре»
`DashboardViewModel` держит сводку пула и обновляет её по событию `IProxyPool.Changed` с throttle
250 мс — событие приходит на каждый исход лизы и на каждую пробу. «Живых» на карточке — это
`LiveCount`, то же число, что читает гейт сбора; `RefreshProxyStats()` публичен, чтобы тесты не ждали
throttle.