librarian: chwilowy timeout Crossrefa nie kasuje już wyszukania #22

Merged
gitea merged 1 commits from librarian-crossref-resilience into main 2026-08-04 13:16:56 +00:00
Owner

Stackowane na #21 (heartbeat) — zmerguj #21 najpierw.

Co się stało (z Twojego loga)

httpx2.ReadTimeout: The read operation timed out
  → habanero: RuntimeError
  → ERROR: Search 1ab5ff00-… crashed

I tu był prawdziwy problem: mój handler crasha traktował to jak poison pill i robił _forget_search() — czyli kasował całe wyszukanie, a watchdog mówił userowi "zeżarło". Wszystko dlatego, że publiczne API mrugnęło raz.

Dwa zabezpieczenia

  1. Retry pojedynczego wywołania Crossrefa — wszystkie trzy wywołania cr.works() idą przez _crossref_call z liniowym backoffem (CONJURER_CROSSREF_ATTEMPTS, domyślnie 4; CONJURER_CROSSREF_BACKOFF, 5s). habanero opakowuje błędy httpx w zwykły RuntimeError, więc nie da się filtrować wąsko — retry jest po prostu ograniczony, a ostatni błąd re-raise'owany. Przy okazji: lecą teraz przez asyncio.to_thread, więc wolny Crossref nie blokuje pętli zdarzeń workera.
  2. Crash nie kasuje wyszukania — licznik prób jest trwale zapisany z requestem, a wyszukanie wraca do kolejki (zachowując checkpoint, więc przerwany skan bazy wznawia się, a nie startuje od zera) aż do CONJURER_SEARCH_MAX_ATTEMPTS (domyślnie 3). W trakcie ponawiania zostaje queued dla watchdoga bota (user nie dostaje fałszywego "zeżarło"). Dopiero po wyczerpaniu prób — odpuszczamy.

Przy okazji

Got blocked. Fuck. ze scrape_bota to normalne zachowanie sci-huba (backoff godzinę i lecimy dalej) → z ERROR na WARNING, żeby przestało udawać awarię, gdy przeglądasz log w poszukiwaniu prawdziwych problemów.

Testy

retry-i-sukces, ograniczony re-raise, brak retry przy sukcesie, trwały licznik prób, forget-po-poddaniu-się. Suite: 58 unit + 70 integration zielone.

🤖 Generated with Claude Code

> **Stackowane na #21** (heartbeat) — zmerguj #21 najpierw. ## Co się stało (z Twojego loga) ``` httpx2.ReadTimeout: The read operation timed out → habanero: RuntimeError → ERROR: Search 1ab5ff00-… crashed ``` I tu był prawdziwy problem: mój handler crasha traktował to jak **poison pill** i robił `_forget_search()` — czyli **kasował całe wyszukanie**, a watchdog mówił userowi "zeżarło". Wszystko dlatego, że publiczne API mrugnęło **raz**. ## Dwa zabezpieczenia 1. **Retry pojedynczego wywołania Crossrefa** — wszystkie trzy wywołania `cr.works()` idą przez `_crossref_call` z liniowym backoffem (`CONJURER_CROSSREF_ATTEMPTS`, domyślnie 4; `CONJURER_CROSSREF_BACKOFF`, 5s). habanero opakowuje błędy httpx w zwykły `RuntimeError`, więc nie da się filtrować wąsko — retry jest po prostu **ograniczony**, a ostatni błąd re-raise'owany. Przy okazji: lecą teraz przez `asyncio.to_thread`, więc wolny Crossref **nie blokuje** pętli zdarzeń workera. 2. **Crash nie kasuje wyszukania** — licznik prób jest **trwale zapisany z requestem**, a wyszukanie **wraca do kolejki** (zachowując checkpoint, więc przerwany skan bazy **wznawia się**, a nie startuje od zera) aż do `CONJURER_SEARCH_MAX_ATTEMPTS` (domyślnie 3). W trakcie ponawiania zostaje `queued` dla watchdoga bota (user nie dostaje fałszywego "zeżarło"). Dopiero po wyczerpaniu prób — odpuszczamy. ## Przy okazji `Got blocked. Fuck.` ze scrape_bota to **normalne** zachowanie sci-huba (backoff godzinę i lecimy dalej) → z `ERROR` na `WARNING`, żeby przestało udawać awarię, gdy przeglądasz log w poszukiwaniu prawdziwych problemów. ## Testy retry-i-sukces, ograniczony re-raise, brak retry przy sukcesie, trwały licznik prób, forget-po-poddaniu-się. Suite: **58 unit + 70 integration** zielone. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
gitea self-assigned this 2026-08-04 11:51:11 +00:00
gitea added 1 commit 2026-08-04 11:51:12 +00:00
Librarian: survive transient Crossref failures instead of losing the search
CI / compile (pull_request) Successful in 12s
CI / unit (pull_request) Successful in 29s
CI / integration (pull_request) Successful in 31s
build / build (push) Successful in 33s
CI / compile (push) Successful in 15s
CI / unit (push) Successful in 30s
CI / integration (push) Successful in 34s
ae1bd67772
Field report: one httpx ReadTimeout inside habanero surfaced as
'Search <uuid> crashed', and the worker's crash handler then FORGOT the
request - so an expensive search vanished and the user was told it was
eaten, because a public API blinked once.

Two defences:
* Every habanero call goes through _crossref_call, which retries with
  linear backoff (CONJURER_CROSSREF_ATTEMPTS, default 4; backoff
  CONJURER_CROSSREF_BACKOFF, 5s). habanero wraps httpx errors in a plain
  RuntimeError so we can't filter narrowly - retries are simply bounded
  and the last error is re-raised. They now also run via asyncio.to_thread,
  so a slow Crossref no longer blocks the worker's event loop.
* A crashed search is no longer dropped on the first failure: the attempt
  count is persisted with the request and the search is requeued (keeping
  any checkpoint, so a crashed DB scan resumes rather than restarts) until
  CONJURER_SEARCH_MAX_ATTEMPTS (default 3). It stays 'queued' for the
  bot's watchdog while retrying, and only after the cap is it forgotten.

Also: scrape_bot's 'Got blocked' is routine sci-hub behaviour (it backs off
an hour and carries on) - log it as WARNING, not ERROR, so it stops looking
like a fault when scanning for real problems.

Tests: retry-then-succeed, bounded re-raise, no retry on success, the
persisted attempt counter, and forget-on-give-up. Suite: 58 unit + 70
integration green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gitea merged commit ae1bd67772 into main 2026-08-04 13:16:56 +00:00
gitea deleted branch librarian-crossref-resilience 2026-08-04 13:16:56 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gitea/conjurer#22