librarian: toleruj złe bajty w chunkach + jawne logowanie wysyłki wyników #4

Merged
gitea merged 1 commits from librarian-unicode-tolerance into main 2026-07-30 15:22:41 +00:00
Owner

Dwa braki odporności zgłoszone z produkcji (na bazie już zmergowanego fixa pętli).

1) UnicodeDecodeError zabijał producenta w połowie pliku

Zabłąkany nie-UTF-8 bajt w chunku (0x96 w zgłoszeniu) rzucał UnicodeDecodeError z readline() — a to ValueError, więc poprzedni except OSError go nie łapał. Sentinel w finally chronił przed zawieszeniem, ale producent ginął w połowie pliku z głośnym tracebackiem, a każdy DOI po feralnym bajcie nie był przeszukany.

Naprawa: chunki otwierane z errors="replace" (zły bajt → U+FFFD; DOI-e są ASCII, więc dopasowanie nigdy nie ucierpi) — plik czyta się do EOF; except producenta rozszerzony z OSError na Exception, więc żaden błąd pojedynczego pliku nie wywali wątku — jest logowany, a sentinel i tak leci.

2) Mechanizm zwrotu wyników — jawne logowanie + odporność

BackgroundTaskSearch._run loguje teraz dokładnie co wychodzi: URL docelowy, uuid, liczbę DOI i samą listę DOI — więc w logu librariana jawnie widać, że wynik został wysłany i co w nim było. Do tego nieudany POST nie jest już fatalny: RequestException wylatywał z pętli workera i zabijał wątek, zawieszając każde kolejne zapytanie do restartu — teraz jest łapany i logowany, a nie-200 od bota logowane jako warning.

Weryfikacja

tests/unit/test_search_bot.py dostaje przypadek: chunk z bajtem 0x96 przed poprawnym DOI, asercja że ten DOI jest znaleziony (plik czytany do końca, nie przerwany). Wszystkie 5 testów search_bot przechodzą.

Dwa braki odporności zgłoszone z produkcji (na bazie już zmergowanego fixa pętli). ## 1) UnicodeDecodeError zabijał producenta w połowie pliku Zabłąkany nie-UTF-8 bajt w chunku (`0x96` w zgłoszeniu) rzucał `UnicodeDecodeError` z `readline()` — a to `ValueError`, więc poprzedni `except OSError` go **nie łapał**. Sentinel w `finally` chronił przed zawieszeniem, ale producent ginął w połowie pliku z głośnym tracebackiem, a **każdy DOI po feralnym bajcie nie był przeszukany**. Naprawa: chunki otwierane z `errors="replace"` (zły bajt → U+FFFD; DOI-e są ASCII, więc dopasowanie nigdy nie ucierpi) — plik czyta się do EOF; `except` producenta rozszerzony z `OSError` na `Exception`, więc żaden błąd pojedynczego pliku nie wywali wątku — jest logowany, a sentinel i tak leci. ## 2) Mechanizm zwrotu wyników — jawne logowanie + odporność `BackgroundTaskSearch._run` loguje teraz **dokładnie co wychodzi**: URL docelowy, uuid, liczbę DOI i samą listę DOI — więc w logu librariana jawnie widać, że wynik został wysłany i co w nim było. Do tego **nieudany POST nie jest już fatalny**: `RequestException` wylatywał z pętli workera i zabijał wątek, zawieszając każde kolejne zapytanie do restartu — teraz jest łapany i logowany, a nie-200 od bota logowane jako warning. ## Weryfikacja `tests/unit/test_search_bot.py` dostaje przypadek: chunk z bajtem `0x96` przed poprawnym DOI, asercja że ten DOI **jest znaleziony** (plik czytany do końca, nie przerwany). Wszystkie 5 testów `search_bot` przechodzą.
gitea self-assigned this 2026-07-30 13:59:42 +00:00
gitea added 1 commit 2026-07-30 13:59:43 +00:00
librarian: tolerate bad bytes in chunks; log what the result-send does
CI / compile (pull_request) Successful in 10s
CI / unit (pull_request) Successful in 20s
CI / integration (pull_request) Successful in 15s
CI / compile (push) Successful in 29s
CI / unit (push) Successful in 30s
CI / integration (push) Successful in 21s
build / build (push) Failing after 7s
031c1f8aea
Two field-reported robustness gaps on top of the hang fix.

1) A stray non-UTF-8 byte in a chunk (0x96 in the report) raised
UnicodeDecodeError from readline() - which is a ValueError, so the earlier
`except OSError` did NOT catch it. The finally-sentinel meant no hang, but the
producer died mid-file with a loud traceback and every DOI after the bad byte
went unsearched. Now chunks are opened with errors="replace" (bad bytes become
U+FFFD; DOIs are ASCII so a match is never affected) so the read runs to EOF,
and the producer's except is broadened from OSError to Exception so no per-file
error can ever crash the thread - it's logged and the sentinel still fires.

2) The result-send back to the bot (BackgroundTaskSearch._run) now logs exactly
what goes out - target URL, uuid, DOI count and the DOI list - so the librarian
log plainly shows a result was sent and what was in it. And a failed POST is no
longer fatal: a RequestException used to propagate out of the worker loop and
kill the thread, stalling every future query until restart; it's now caught and
logged, and a non-200 from the bot is logged as a warning.

Verified: tests/unit/test_search_bot.py gains a case writing a chunk with a 0x96
byte before a valid DOI and asserting that DOI is still found (file read to
completion, not aborted). All 5 search_bot unit tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gitea merged commit 031c1f8aea into main 2026-07-30 15:22:41 +00:00
gitea deleted branch librarian-unicode-tolerance 2026-07-30 15:22:42 +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#4