tradein/browser: сайдкар не считает восстановимой ошибку «execution context was destroyed» — страница уходит в навигацию между goto и evaluate, ретрая нет #2676

Closed
opened 2026-08-05 19:37:02 +00:00 by bot-backend · 1 comment
Collaborator

Найдено при разборе денежного кластера домовой оценки (#2674, PR #2675). Отдельная задача, потому что сайдкар общий для Авито, Циана и Домклика — свой радиус поражения.

Что происходит

Обработчик получения JSON делает page.goto(origin, wait_until="domcontentloaded"), ждёт фиксированную паузу и вызывает page.evaluate(...). За это время страница Авито успевает уйти в клиентскую навигацию, контекст исполнения пересоздаётся, и вызов падает с Page.evaluate: Execution context was destroyed.

Ретрая не происходит, потому что распознавание восстановимого сбоя матчит только четыре строки: закрытие цели, закрытие браузера, закрытие соединения и отключение браузера. «Execution context was destroyed» в этот список не входит — значит для нас это не сбой браузера, а окончательный отказ.

Проверено по логам сайдкара: один и тот же адрес падает дважды подряд, а соседние адреса в том же прогоне отдают 200. То есть это гонка, а не недоступность.

Сколько стоит

В прогоне домовой оценки 5 августа — 23 отказа из 50 попыток по этой причине. Это была вторая по величине причина после отсутствия прокси.

Поскольку сайдкар общий, та же гонка бьёт по детальным проходам Циана и Домклика, просто там её никто не выделял из общего числа ошибок.

Чем чинить

Два варианта, оба маленькие:

  1. Добавить строку в распознавание восстановимого сбоя — тогда сработает существующий ретрай. Самое дешёвое, но лечит симптом.
  2. Ждать load вместо фиксированной паузы — тогда гонка не возникает вовсе. Честнее, но надо проверить, не удлинит ли это прогон на страницах, которые «дозагружаются» бесконечно.

Разумно сделать оба: ожидание убирает большинство случаев, ретрай страхует остаток.

Оговорка

Числа сняты с домовой оценки Авито — на других источниках долю этой ошибки я не мерил. Перед правкой стоит посчитать её по всем потребителям сайдкара, чтобы понимать реальный выигрыш.

Связано: #2674, #2675, #2616 (отказ ходить без прокси), #2638.

Найдено при разборе денежного кластера домовой оценки (#2674, PR #2675). Отдельная задача, потому что сайдкар общий для Авито, Циана и Домклика — свой радиус поражения. ## Что происходит Обработчик получения JSON делает `page.goto(origin, wait_until="domcontentloaded")`, ждёт **фиксированную** паузу и вызывает `page.evaluate(...)`. За это время страница Авито успевает уйти в клиентскую навигацию, контекст исполнения пересоздаётся, и вызов падает с `Page.evaluate: Execution context was destroyed`. Ретрая не происходит, потому что распознавание восстановимого сбоя матчит только четыре строки: закрытие цели, закрытие браузера, закрытие соединения и отключение браузера. «Execution context was destroyed» в этот список не входит — значит для нас это не сбой браузера, а окончательный отказ. Проверено по логам сайдкара: один и тот же адрес падает дважды подряд, а соседние адреса **в том же прогоне** отдают 200. То есть это гонка, а не недоступность. ## Сколько стоит В прогоне домовой оценки 5 августа — **23 отказа из 50** попыток по этой причине. Это была вторая по величине причина после отсутствия прокси. Поскольку сайдкар общий, та же гонка бьёт по детальным проходам Циана и Домклика, просто там её никто не выделял из общего числа ошибок. ## Чем чинить Два варианта, оба маленькие: 1. **Добавить строку в распознавание восстановимого сбоя** — тогда сработает существующий ретрай. Самое дешёвое, но лечит симптом. 2. **Ждать `load` вместо фиксированной паузы** — тогда гонка не возникает вовсе. Честнее, но надо проверить, не удлинит ли это прогон на страницах, которые «дозагружаются» бесконечно. Разумно сделать оба: ожидание убирает большинство случаев, ретрай страхует остаток. ## Оговорка Числа сняты с домовой оценки Авито — на других источниках долю этой ошибки я не мерил. Перед правкой стоит посчитать её по всем потребителям сайдкара, чтобы понимать реальный выигрыш. Связано: #2674, #2675, #2616 (отказ ходить без прокси), #2638.
Author
Collaborator

Закрыто двумя PR: #2716 (классификация) и #2721 (сам повтор). Прод-верификация — живым запросом, не «деплой зелёный».

Опровергнутые предпосылки из issue:

  1. «Ретрая не происходит» — на клиенте он БЫЛ: BrowserFetcher.fetch_json ловит httpx.HTTPError (⊃ HTTPStatusError) и повторяет один раз. Наблюдение «один и тот же адрес падает дважды подряд» — это и есть попытка + повтор, т.е. доказательство, что повтор «в лоб» не работает.
  2. «23 отказа из 50 по этой причине» — проверить уже нельзя: причина отказа до #2708 выбрасывалась клиентом (в БД только «Server error '500 ...' for url .../fetch-json»), а логи сайдкара за 05.08 стёрты пересозданием контейнера. Из прода достоверно: прогон 05.08 = checked 50 / errors 35 / skipped 15, из них 23 дома — 500 от сайдкара, 12 — 403 площадки. Что именно стояло за 500, разделить нельзя: в живом окне логов тот же код давал NS_ERROR_PROXY_BAD_GATEWAY.
  3. «Ждать load вместо фиксированной паузы — гонка не возникнет вовсе» — неверно. Прод 06.08: навигация origin-страницы происходит ПОСЛЕ load, ожидание её не опережает. Повтор на свежей странице тоже не помогает — он воспроизводит ту же первую навигацию (две попытки подряд падали одинаково).

Что оказалось верным: сама ошибка реальна и воспроизводится вживую на безобидном robots.txt; /fetch того же URL при этом отдаёт 200 — значит это не бан и не мёртвый прокси.

Итог (прод, 10:22 UTC): гонка случилась (WARNING контекст страницы пересоздан навигацией, повтор evaluate на той же странице), повтор отработал, ответ 200 вместо 500 за 1.5с.

Побочная находка, отдельная от этой задачи: тесты tradein-mvp/browser/test_server*.py НЕ запускаются в CI (ci-tradein.yml гоняет только tradein-mvp/backend), и 4 из них лежат красными на main до моих правок (_ensure_browser(proxy_override=...), пейсинг).

Закрыто двумя PR: #2716 (классификация) и #2721 (сам повтор). Прод-верификация — живым запросом, не «деплой зелёный». **Опровергнутые предпосылки из issue:** 1. «Ретрая не происходит» — на клиенте он БЫЛ: `BrowserFetcher.fetch_json` ловит `httpx.HTTPError` (⊃ `HTTPStatusError`) и повторяет один раз. Наблюдение «один и тот же адрес падает дважды подряд» — это и есть попытка + повтор, т.е. доказательство, что повтор «в лоб» не работает. 2. «23 отказа из 50 по этой причине» — проверить уже нельзя: причина отказа до #2708 выбрасывалась клиентом (в БД только «Server error '500 ...' for url .../fetch-json»), а логи сайдкара за 05.08 стёрты пересозданием контейнера. Из прода достоверно: прогон 05.08 = `checked 50 / errors 35 / skipped 15`, из них **23 дома** — 500 от сайдкара, 12 — 403 площадки. Что именно стояло за 500, разделить нельзя: в живом окне логов тот же код давал `NS_ERROR_PROXY_BAD_GATEWAY`. 3. «Ждать `load` вместо фиксированной паузы — гонка не возникнет вовсе» — **неверно**. Прод 06.08: навигация origin-страницы происходит ПОСЛЕ `load`, ожидание её не опережает. Повтор на свежей странице тоже не помогает — он воспроизводит ту же первую навигацию (две попытки подряд падали одинаково). **Что оказалось верным:** сама ошибка реальна и воспроизводится вживую на безобидном `robots.txt`; `/fetch` того же URL при этом отдаёт 200 — значит это не бан и не мёртвый прокси. **Итог (прод, 10:22 UTC):** гонка случилась (`WARNING контекст страницы пересоздан навигацией, повтор evaluate на той же странице`), повтор отработал, ответ **200** вместо 500 за 1.5с. Побочная находка, отдельная от этой задачи: тесты `tradein-mvp/browser/test_server*.py` НЕ запускаются в CI (`ci-tradein.yml` гоняет только `tradein-mvp/backend`), и 4 из них лежат красными на main до моих правок (`_ensure_browser(proxy_override=...)`, пейсинг).
Sign in to join this conversation.
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#2676
No description provided.