fix(tradein/domclick): таймаут сайдкара и пустой пул считались отказами площадки (#3283) #3286

Merged
lekss361 merged 1 commit from fix/3283-non-blocks-counted-as-blocks into main 2026-08-30 13:42:11 +00:00
Owner

Закрывает #3283. Найдено при попытке загрузить очередь Домклика (2 001 объявление без карточки) — она не грузится именно из-за этого.

Доказательство

Прогон 5399 (30.08, батч 400) умер по правилу «3 блока подряд», не обогатив ни одной карточки. Из трёх засчитанных блоков отказом Домклика не был ни один:

BLOCKED #1 (kind=unknown): 500 Internal Server Error — TimeoutError: Page.goto
BLOCKED #2 (kind=unknown): 500 Internal Server Error — TimeoutError: Page.goto
BLOCKED #3 (kind=unknown): no proxy available for provider='domclick' (pool empty)
ABORT -- 3 consecutive blocks, диагнозы: {'unknown': 3}. enriched=0 attempted=3

Третий случай особенно нагляден: запрос к площадке не отправлялся вовсе. NoProxyAvailableError документирован дословно как «НАША инфраструктура, не внешний блок» и поднимается до HTTP-запроса — считать его отказом Домклика прямо противоречит его же контракту.

Лог абортa сам печатал диагнозы: {'unknown': 3}, то есть информация была, но в решении не участвовала.

Почему нельзя было чинить по статусу

Первая попытка — «считать блоками только kind == platform» — уронила четыре существующих теста, и правильно сделала: настоящий QRATOR-челлендж приходит без HTTP-статуса, то есть по статусу неотличим от таймаута навигации. Классификация по коду ответа здесь не работает в принципе.

Различима природа исключения, причём разделение уже проведено в fetch_detail:

путь что это признак
except SidecarBanPageError генуинный отказ, там же report_ban нет httpx в цепочке
parse_detail_htmlDomClickBlockedError маркеры в HTML, генуинный нет httpx в цепочке
except Exceptionraise ... from exc таймаут / 5xx / транспорт httpx-ошибка в __cause__

Отсюда и признак: httpx-ошибка в цепочке причин. Он не опирается ни на статус, ни на текст сообщения.

Правка

  1. Пустой пул — не блок, а остановка. Продолжать цикл бессмысленно: каждая следующая карточка упрётся в то же самое. Прогон уходит в failed с внятной причиной, а не в banned — запись не должна утверждать про площадку то, чего не было.
  2. Транспортные сбои идут в failed, туда же, куда давно идёт DomClickParseError (он честно помечен «schema drift, not a block»), и счётчик блоков не трогают.
  3. Отдельный сторож на молчаливый отказ: max_consecutive_soft_failures=10. Если площадка отказывает молча и сайдкар не отдал статус, прогон обязан остановиться — просто позже. Без этого снятие хайртриггера оставило бы прогон вовсе без тормоза.

Порог для сбоев выше блочного намеренно: цена ошибки несимметрична. Ложный обрыв стоит целого прогона и часов простоя узла, лишний запрос к живой площадке — одного запроса. Та же логика, что в #3272.

Тесты

10 проверок, включая дословный сценарий 5399 и страховку «генуинные блоки по-прежнему рвут прогон после трёх». Проверено мутациями:

мутация падает
вернуть пустой пул в блоки 2
вернуть транспорт в блоки 6
снять сторож на молчаливый отказ 2

Отдельно показательно: четыре существовавших теста на генуинный блок проходят без единой правки. Это и есть признак того, что разделение проведено по верному основанию — старое поведение для настоящих отказов сохранено побайтово.

Набор целиком — 5206 passed, 37 skipped.

Мелочь

В теле коммита одна строка потеряла фрагмент: обратные кавычки в -m были съедены оболочкой. Смысл строки — тот, что в таблице выше; правку истории не делал.

Закрывает #3283. Найдено при попытке загрузить очередь Домклика (2 001 объявление без карточки) — она не грузится именно из-за этого. ## Доказательство Прогон 5399 (30.08, батч 400) умер по правилу «3 блока подряд», не обогатив **ни одной** карточки. Из трёх засчитанных блоков отказом Домклика не был ни один: ``` BLOCKED #1 (kind=unknown): 500 Internal Server Error — TimeoutError: Page.goto BLOCKED #2 (kind=unknown): 500 Internal Server Error — TimeoutError: Page.goto BLOCKED #3 (kind=unknown): no proxy available for provider='domclick' (pool empty) ABORT -- 3 consecutive blocks, диагнозы: {'unknown': 3}. enriched=0 attempted=3 ``` Третий случай особенно нагляден: запрос к площадке **не отправлялся вовсе**. `NoProxyAvailableError` документирован дословно как «НАША инфраструктура, не внешний блок» и поднимается до HTTP-запроса — считать его отказом Домклика прямо противоречит его же контракту. Лог абортa сам печатал `диагнозы: {'unknown': 3}`, то есть информация была, но в решении не участвовала. ## Почему нельзя было чинить по статусу Первая попытка — «считать блоками только `kind == platform`» — уронила четыре существующих теста, и правильно сделала: **настоящий QRATOR-челлендж приходит без HTTP-статуса**, то есть по статусу неотличим от таймаута навигации. Классификация по коду ответа здесь не работает в принципе. Различима **природа исключения**, причём разделение уже проведено в `fetch_detail`: | путь | что это | признак | |---|---|---| | `except SidecarBanPageError` | генуинный отказ, там же `report_ban` | нет httpx в цепочке | | `parse_detail_html` → `DomClickBlockedError` | маркеры в HTML, генуинный | нет httpx в цепочке | | `except Exception` → `raise ... from exc` | таймаут / 5xx / транспорт | **httpx-ошибка в `__cause__`** | Отсюда и признак: httpx-ошибка в цепочке причин. Он не опирается ни на статус, ни на текст сообщения. ## Правка 1. **Пустой пул — не блок, а остановка.** Продолжать цикл бессмысленно: каждая следующая карточка упрётся в то же самое. Прогон уходит в `failed` с внятной причиной, а не в `banned` — запись не должна утверждать про площадку то, чего не было. 2. **Транспортные сбои идут в `failed`**, туда же, куда давно идёт `DomClickParseError` (он честно помечен «schema drift, not a block»), и счётчик блоков не трогают. 3. **Отдельный сторож на молчаливый отказ:** `max_consecutive_soft_failures=10`. Если площадка отказывает молча и сайдкар не отдал статус, прогон обязан остановиться — просто позже. Без этого снятие хайртриггера оставило бы прогон вовсе без тормоза. Порог для сбоев выше блочного намеренно: цена ошибки несимметрична. Ложный обрыв стоит целого прогона и часов простоя узла, лишний запрос к живой площадке — одного запроса. Та же логика, что в #3272. ## Тесты 10 проверок, включая дословный сценарий 5399 и страховку «генуинные блоки по-прежнему рвут прогон после трёх». Проверено мутациями: | мутация | падает | |---|---| | вернуть пустой пул в блоки | 2 | | вернуть транспорт в блоки | 6 | | снять сторож на молчаливый отказ | 2 | Отдельно показательно: **четыре существовавших теста на генуинный блок проходят без единой правки.** Это и есть признак того, что разделение проведено по верному основанию — старое поведение для настоящих отказов сохранено побайтово. Набор целиком — `5206 passed, 37 skipped`. ## Мелочь В теле коммита одна строка потеряла фрагмент: обратные кавычки в `-m` были съедены оболочкой. Смысл строки — тот, что в таблице выше; правку истории не делал.
lekss361 added 1 commit 2026-08-30 13:34:39 +00:00
fix(tradein/domclick): таймаут сайдкара и пустой пул считались отказами площадки (#3283)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m56s
42913d2843
Прогон 5399 умер по правилу «3 блока подряд», не обогатив ни одной карточки
из 400. Отказом Домклика не был ни один из трёх: два — HTTP 500 от сайдкара
(таймаут навигации), третий — пустой пул, при котором запрос к площадке не
отправлялся вовсе. Лог абортa сам это печатал: диагнозы {'unknown': 3}.

Различать по HTTP-статусу нельзя: настоящий QRATOR-челлендж приходит без
статуса и неотличим от таймаута — на этом и стояли прежние тесты. Различима
природа исключения, причём разделение уже проведено в fetch_detail: генуинный
бан приезжает через SidecarBanPageError или маркеры в parse_detail_html, а
транспорт — через  с , то есть с
httpx-ошибкой в цепочке причин.

Три правки:
* пустой пул (NoProxyAvailableError в цепочке) — не блок, а остановка прогона:
  продолжать бессмысленно, каждая следующая карточка упрётся в то же. Прогон
  уходит в failed, а не banned — запись не должна утверждать про площадку то,
  чего не было;
* транспортные сбои идут в failed, туда же, куда давно идёт DomClickParseError
  («schema drift, not a block»), и счётчик блоков не трогают;
* отдельный сторож на молчаливый отказ: max_consecutive_soft_failures=10, иначе
  снятие хайртриггера оставило бы прогон без тормоза вовсе.

Тесты: 10 проверок, включая дословный сценарий 5399 и страховку «генуинные
блоки по-прежнему рвут прогон после трёх». Проверено мутациями: возврат пустого
пула в блоки роняет 2, возврат транспорта — 6, снятие сторожа — 2. Четыре
существовавших теста на генуинный блок продолжают проходить без правки — это и
показывает, что разделение проведено по верному признаку. Набор целиком —
5206 passed, 37 skipped.
lekss361 merged commit 03d04f13b5 into main 2026-08-30 13:42:11 +00:00
lekss361 deleted branch fix/3283-non-blocks-counted-as-blocks 2026-08-30 13:42:11 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3286
No description provided.