librarian is_alive: pełny round-trip ping przez kolejkę, nie samo GET #10
Reference in New Issue
Block a user
Delete Branch "librarian-ping-healthcheck"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Po co
Health-check librariana był zwykłym GET
/— sprawdzał tylko, że Flask nasłuchuje. Nie mówił nic o tym, czy librarian realnie przyjmie zapytanie, przepuści je przez swoją wewnętrzną kolejkę i worker, i odeśle odpowiedź. Cog mógł się włączyć przy librarianie z zawieszonym workerem albo takim, który nie ma drogi powrotnej do bota.Co robi ping
Zapytanie testowe idzie dokładnie tą samą drogą co prawdziwe wyszukanie, po obu stronach:
Cog włącza się tylko gdy cała ta pętla domknie się w 3s. Efekt uboczny: ping potwierdza też drogę powrotną librarian→bot, czego GET nigdy nie robił.
Bezpieczeństwo / brak blokad (wprost z wymagań)
uuid4).waitsą ograniczone czasowo; health-check biegnie wasyncio.to_thread, więc nie blokuje pętli zdarzeń; endpoint/pingpo stronie librariana zwraca 200 od razu (tylko wrzuca do kolejki).awaiting_qpoPING_TTL_SECONDS(30s). Wszystkie zapisy doawaiting_qzostają wscan_queue(append) iscan_incoming(remove) — bez locków, bez mutacji między wątkami.IN_COMM_Qjako „Orphaned" — inaczej cog wyplułby na Discorda fałszywe „nie ma nic".Pliki
communication_subroutine.py—librarian_ping(), obsługa pongu + sweep wscan_incoming.conjurer_librarian/conjurer_librarian.py— endpointPOST /ping+ gałąź ping w workerze (pong bez wyszukiwania).bot.py— librarian gatowany przezlibrarian_ping(pozostałe usługi bez zmian).constants.py—LIBRARIAN_PING = "/ping".Testy
Nowy
tests/integration/test_librarian_ping.py(stub podszywa się pod librariana): OK round-trip, timeout gdy przyjęte-ale-bez-pongu, unreachable, non-200, porzucenie sierocego pongu, oraz że normalne wyniki nadal docierają do IN_COMM_Q. Lokalnie: 24 integration + 41 unit zielone.Obserwacja na przyszłość
Jeśli librarian akurat mieli wielogodzinne wyszukanie, ping czeka za nim w kolejce i po 3s cog zostaje wyłączony — watchdog spróbuje ponownie za 300s. To świadomie zachowawcze (ping idzie przez kolejkę, nie omija jej). Gdyby przeszkadzało, można dać pingowi osobny, priorytetowy tor.
🤖 Generated with Claude Code
The librarian health check was a plain GET to '/', which only proved Flask was listening - not that the service could actually take a query, run it through its internal queue+worker, and answer back. So the cog could load against a librarian whose worker was wedged or that couldn't reach the bot on the return leg. Replace it with a ping that travels the SAME path a real search does, on both sides: bot: QueryControl -> OUT_COMM_Q -> scan_queue -> awaiting_q librarian: POST /ping -> librarian_queue -> worker pulls it off (no Crossref/DOI search) -> pongs back with the same uuid bot: /conjurer -> incoming_q -> scan_incoming matches uuid, wakes waiter The cog enables only when that whole loop closes within 3s. This also proves the librarian->bot return path, which a GET never did. Safety: uuid is random per ping; the wait and POST are both bounded so startup can't stall; a pong that finds no waiter is dropped (never orphaned into IN_COMM_Q, which would make the cog post a bogus 'no results' message); and a ping whose pong never returns is swept out of awaiting_q after PING_TTL_SECONDS so nothing leaks. All awaiting_q writes stay within scan_queue (append) and scan_incoming (remove) - no locks, no cross-thread mutation. Integration tests cover: OK round-trip, timeout when accepted-but-no-pong, unreachable, non-200, orphan-pong-dropped, and that real results still reach IN_COMM_Q. Suite: 24 integration + 41 unit green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>c36d6d3fc2toed8b271b4eAktualizacja zakresu — druga część (commit
ce18b38)Po uwadze recenzenta dorobione dwa mechanizmy rozróżniające przypadki awarii:
1. Ping świadomy "mielenia" (przypadek b — zerwana ścieżka powrotna)
Gdy worker akurat mieli wyszukanie, ping nie czeka już w kolejce za nim (co robiło ze zdrowego-ale-zajętego librariana "martwego"). Librarian trzyma flagę
worker_busyi przy zajętości odsyła pong od razu, bez kolejki. Zajętość = zdrowie, można dokładać kolejne wyszukiwania. Ping i tak przechodzi ścieżką powrotną librarian→bot, więc dalej łapie to, co ma łapać: zerwaną/niekompatybilną ścieżkę powrotną, na której zapytania giną. Ping w stanie bezczynności nadal idzie przez kolejkę wewnętrzną.2. Watchdog per-zapytanie (przypadek a — skończone, ale wynik zginął)
Librarian śledzi cykl życia każdego uuid (
queued → processing → zniknął) wactive_queries, wystawione przez nowePOST /query_status. Po wysłaniu zapytania bot zapisuje je wself.pending;watch_pendingodpytuje/query_status. Dopóki librarian zna uuid — zapytanie idzie, nie ruszamy. Gdy uuid zniknie u librariana, a u bota wciąż jest pending → wynik policzony, ale niedostarczony: po oknie karencji (żeby odsiać wynik "w locie") bot wrzuca info na czat — tylko wtedy. Normalnie dostarczony wynik jest zdejmowany zself.pendingprzezcheck_data_qi nigdy nie trafia na ścieżkę alarmu.Hardening: ciało wyszukiwania w workerze owinięte w try/except/finally — crash pojedynczego wyszukiwania nie zabije wątku (nie zamrozi kolejki), a
worker_busy/active_querieszawsze się czyszczą. Logika karencji wydzielona do bezzależnościowegolibrarian_watchdog.pending_verdict(unit-testowalna bez discord/pdf)./pingi/query_statussą sync, więc działają bezflask[async].Testy: unit
test_librarian_watchdog(przejścia werdyktu) + integrationtest_librarian_query_lifecycle(query_status known/unknown + auth, idle-ping-do-kolejki, busy-ping-pong-bezpośredni). Suite: 28 integration + 48 unit zielone.ce18b386d3to5d321f2f5b