implement cancellation handling in CaptureService and Database, update job states accordingly

This commit is contained in:
kstyagi23
2026-09-05 06:54:10 +05:30
parent 9c1a301c1a
commit cab7a68b8b
7 changed files with 293 additions and 38 deletions
+30 -5
View File
@@ -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,