fix(pmc): не давать залипшему запросу съесть весь бюджет прогона
Таймаут запроса к бакету был 60с при бюджете таска 1500с — один залипший GET отъедал почти весь прогон. Плюс map() отдаёт результаты строго по порядку отправки, поэтому один медленный запрос блокировал все 11 уже готовых потоков. Таймаут снижен до 12с (обычный GET укладывается в доли секунды, 12с — запас на джиттер), map() заменён на as_completed: готовые результаты отдаются сразу, не дожидаясь залипшего соседа. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
@@ -39,8 +39,11 @@ class PMCBulkParser(BaseParser):
|
|||||||
def __init__(self, workers: int = 12) -> None:
|
def __init__(self, workers: int = 12) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.workers = workers
|
self.workers = workers
|
||||||
|
# 60с на файл при бюджете задачи в 1500с — один залипший запрос съедал
|
||||||
|
# почти весь бюджет. Обычный GET сюда укладывается в доли секунды,
|
||||||
|
# 12с — с большим запасом на джиттер, но без риска съесть весь прогон
|
||||||
self.client = httpx.Client(
|
self.client = httpx.Client(
|
||||||
timeout=60,
|
timeout=12,
|
||||||
headers={"User-Agent": "AcademicHelper/1.0 (noreply@jze9.ru)"},
|
headers={"User-Agent": "AcademicHelper/1.0 (noreply@jze9.ru)"},
|
||||||
)
|
)
|
||||||
# Позиция листинга: бакет отдаётся страницами, и продолжать прогон
|
# Позиция листинга: бакет отдаётся страницами, и продолжать прогон
|
||||||
@@ -82,8 +85,13 @@ class PMCBulkParser(BaseParser):
|
|||||||
logger.info("PMC bulk: бакет закончился")
|
logger.info("PMC bulk: бакет закончился")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# as_completed вместо map(): map() отдаёт результаты строго по
|
||||||
|
# порядку отправки, поэтому один залипший запрос блокирует все
|
||||||
|
# уже готовые — даже если остальные 11 потоков давно отработали
|
||||||
with ThreadPoolExecutor(max_workers=self.workers) as pool:
|
with ThreadPoolExecutor(max_workers=self.workers) as pool:
|
||||||
for raw in pool.map(self._fetch_article, ids):
|
futures = {pool.submit(self._fetch_article, i): i for i in ids}
|
||||||
|
for future in as_completed(futures):
|
||||||
|
raw = future.result()
|
||||||
if raw is None:
|
if raw is None:
|
||||||
continue
|
continue
|
||||||
given += 1
|
given += 1
|
||||||
|
|||||||
Reference in New Issue
Block a user