implement cancellation handling in CaptureService and Database, update job states accordingly
This commit is contained in:
+30
-5
@@ -22,6 +22,9 @@ from app.crawler import (
|
||||
from app.db import Database
|
||||
from app.url_policy import NetworkRules, UrlPolicyError, resolve_and_validate
|
||||
|
||||
CANCELLATION_POLL_SECONDS = 0.25
|
||||
PROGRESS_WRITE_INTERVAL_SECONDS = 0.5
|
||||
|
||||
|
||||
def _remove_tree(path: Path, allowed_root: Path) -> None:
|
||||
"""Delete only a child path beneath the configured work root."""
|
||||
@@ -104,15 +107,28 @@ class CaptureService:
|
||||
report_path = self.settings.reports_dir / f"{job_id}-{attempt_id}.json"
|
||||
heartbeat_stop = threading.Event()
|
||||
lease_lost = threading.Event()
|
||||
cancellation_requested = threading.Event()
|
||||
cancellation_lock = threading.Lock()
|
||||
heartbeat_thread: threading.Thread | None = None
|
||||
capture_deadline = time.monotonic() + int(config["max_duration_seconds"])
|
||||
last_cancellation_check = 0.0
|
||||
last_progress_at = 0.0
|
||||
last_progress_phase: str | None = None
|
||||
|
||||
def capture_cancelled() -> bool:
|
||||
return (
|
||||
lease_lost.is_set()
|
||||
or self.database.is_cancel_requested(job_id)
|
||||
or not self.database.owns_lease(job_id, self.worker_id)
|
||||
)
|
||||
nonlocal last_cancellation_check
|
||||
if lease_lost.is_set() or cancellation_requested.is_set():
|
||||
return True
|
||||
now = time.monotonic()
|
||||
with cancellation_lock:
|
||||
if now - last_cancellation_check < CANCELLATION_POLL_SECONDS:
|
||||
return False
|
||||
last_cancellation_check = now
|
||||
# Fetch workers call this predicate for every response chunk. Poll SQLite sparingly.
|
||||
if self.database.worker_should_stop(job_id, self.worker_id):
|
||||
cancellation_requested.set()
|
||||
return True
|
||||
return False
|
||||
|
||||
def renew_lease() -> None:
|
||||
interval = min(20, max(3, self.settings.lease_seconds // 3))
|
||||
@@ -170,6 +186,15 @@ class CaptureService:
|
||||
def update(
|
||||
phase: str, message: str, stats: dict[str, int], warnings: list[str]
|
||||
) -> None:
|
||||
nonlocal last_progress_at, last_progress_phase
|
||||
now = time.monotonic()
|
||||
if (
|
||||
phase == last_progress_phase
|
||||
and now - last_progress_at < PROGRESS_WRITE_INTERVAL_SECONDS
|
||||
):
|
||||
return
|
||||
last_progress_at = now
|
||||
last_progress_phase = phase
|
||||
renewed = self.database.update_progress(
|
||||
job_id,
|
||||
self.worker_id,
|
||||
|
||||
Reference in New Issue
Block a user