fix: eliminate greenlet crash + memory leak protection
- docker-compose: switch worker to --pool=solo --concurrency=1 --max-tasks-per-child=1 - tasks.py: remove ThreadPoolExecutor wrapper from _run_browser_job (greenlet crash) - scraper.py: browser fallback runs sequentially instead of ThreadPoolExecutor - scraper.py: add gc.collect() after scraper close to prevent memory leaks
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import gc
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@@ -201,6 +202,7 @@ class IAAIScraper:
|
||||
pass
|
||||
finally:
|
||||
self._http_pool = None
|
||||
gc.collect()
|
||||
|
||||
def _new_context(self) -> BrowserContext:
|
||||
if self.browser is None:
|
||||
@@ -1623,23 +1625,17 @@ class IAAIScraper:
|
||||
"protection_events": local_protection,
|
||||
}
|
||||
|
||||
# Одна страница — в главном потоке.
|
||||
if n_pages == 1:
|
||||
res = _process_slice(slices[0] if slices else [])
|
||||
# Playwright sync API использует greenlets, привязанные к потоку.
|
||||
# Запуск в ThreadPoolExecutor вызывает greenlet crash.
|
||||
# Обрабатываем все слайсы последовательно в текущем потоке.
|
||||
for s in slices:
|
||||
if not s:
|
||||
continue
|
||||
res = _process_slice(s)
|
||||
records.extend(res["records"])
|
||||
failures.extend(res["failures"])
|
||||
cars_failed += res["cars_failed"]
|
||||
protection_events += res["protection_events"]
|
||||
else:
|
||||
# Несколько страниц — параллельно, каждый поток со своей Page.
|
||||
with ThreadPoolExecutor(max_workers=n_pages) as executor:
|
||||
futures = [executor.submit(_process_slice, s) for s in slices if s]
|
||||
for fut in as_completed(futures):
|
||||
res = fut.result()
|
||||
records.extend(res["records"])
|
||||
failures.extend(res["failures"])
|
||||
cars_failed += res["cars_failed"]
|
||||
protection_events += res["protection_events"]
|
||||
|
||||
return {
|
||||
"records": records,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Задачи Celery для синхронизации автомобилей и листинга IAAI.
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@@ -61,35 +60,11 @@ def _retry_with_backoff(func, *, attempts: int = 5, base_delay_s: float = 1.0):
|
||||
|
||||
|
||||
def _run_browser_job(func, *args, **kwargs):
|
||||
# Браузерный код запускаем в отдельном потоке без активного loop.
|
||||
# НЕ используем `with` — иначе shutdown(wait=True) заблокирует main thread
|
||||
# если SoftTimeLimitExceeded прервёт future.result(), а browser thread ещё работает.
|
||||
settings = Settings()
|
||||
soft = settings.celery.task_soft_time_limit
|
||||
hard = settings.celery.task_time_limit
|
||||
# Таймаут для future.result(): берём hard limit (или soft + 120), чтобы не висеть вечно.
|
||||
wait_timeout = min(hard, soft + 120) if soft and hard else None
|
||||
|
||||
executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="iaai-browser")
|
||||
future = executor.submit(func, *args, **kwargs)
|
||||
try:
|
||||
result = future.result(timeout=wait_timeout)
|
||||
except SoftTimeLimitExceeded:
|
||||
# Отпускаем executor без ожидания — thread умрёт когда Celery убьёт процесс (hard limit).
|
||||
future.cancel()
|
||||
executor.shutdown(wait=False, cancel_futures=True)
|
||||
raise
|
||||
except TimeoutError:
|
||||
# future.result(timeout=...) вышел по таймауту — browser thread завис.
|
||||
future.cancel()
|
||||
executor.shutdown(wait=False, cancel_futures=True)
|
||||
raise SoftTimeLimitExceeded("Browser thread did not finish within time limit")
|
||||
except Exception:
|
||||
executor.shutdown(wait=False, cancel_futures=True)
|
||||
raise
|
||||
else:
|
||||
executor.shutdown(wait=True)
|
||||
return result
|
||||
# С pool=solo Celery worker работает в одном процессе/потоке.
|
||||
# Playwright sync API использует greenlets, которые привязаны к потоку.
|
||||
# Запуск в отдельном потоке вызывает greenlet.error: cannot switch to a different thread.
|
||||
# Поэтому запускаем напрямую в текущем потоке.
|
||||
return func(*args, **kwargs)
|
||||
|
||||
|
||||
def _sync_listing_lock_ttl_seconds() -> int:
|
||||
|
||||
Reference in New Issue
Block a user