Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions backend/app/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,8 @@ async def analyze_paper(
# --- Asynchronous analysis queue (BackgroundTasks) ---
# Shared in-memory task store: task_id -> {"status": "queued"|"processing"|"done"|"error", "result": ...}
_task_store: dict = {}
_task_expiry: dict = {}
MAX_TASK_AGE_SEC = 600 # Evict tasks after 10 minutes to save RAM


@router.post("/analyze/async")
Expand Down Expand Up @@ -438,11 +440,20 @@ async def analyze_paper_async(
@router.get("/analyze/{task_id}/status")
def get_analysis_status(task_id: str, db: Session = Depends(get_db)):
"""Poll the status of an async analysis. Returns status and full result when done."""
# Periodic cleanup of old tasks to free memory
now = time.time()
expired = [tid for tid, ts in _task_expiry.items() if now > ts]
for tid in expired:
_task_store.pop(tid, None)
_task_expiry.pop(tid, None)

task = _task_store.get(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found or expired")

if task["status"] == "done" and task["result"]:
# Mark for eviction soon after it's been read
_task_expiry[task_id] = now + 60
return {
"status": "done",
"record_id": task["record_id"],
Expand Down Expand Up @@ -633,11 +644,17 @@ async def _process_analysis_task(task_id: str):

# Clean up payload from memory
task["payload"] = None
_task_expiry[task_id] = time.time() + MAX_TASK_AGE_SEC
import gc
gc.collect()
except Exception as exc:
logger.exception("Async analysis task failed for %s", task_id)
task["status"] = "error"
task["error"] = str(exc)
task["payload"] = None
_task_expiry[task_id] = time.time() + MAX_TASK_AGE_SEC
import gc
gc.collect()


def _build_provenance(record) -> Optional[dict]:
Expand Down
6 changes: 6 additions & 0 deletions backend/app/services/ai_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ def trained_ai_score(text: str) -> Optional[float]:
except Exception as exc: # pragma: no cover
logger.error("Trained model inference failed: %s", exc)
return None
finally:
import gc
gc.collect()


# --- Transformer tier (Hugging Face) ------------------------------------------
Expand Down Expand Up @@ -383,6 +386,9 @@ def transformer_ai_score(text: str) -> Optional[float]:
except Exception as exc: # pragma: no cover
logger.error("Transformer inference failed: %s", exc)
return None
finally:
import gc
gc.collect()


# --- Public API ---------------------------------------------------------------
Expand Down
6 changes: 6 additions & 0 deletions backend/app/services/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,10 @@ def write_pdf_report(doc, record, breakdown, citation_result, statistics_result,
story.append(Paragraph("<i>Generated by VeriPaper Autonomous Platform. This report is intended for academic assistance only.</i>", styles['Normal']))

doc_pdf.build(story)

# Emergency memory cleanup: clear large lists and trigger GC
story.clear()
import gc
gc.collect()

logger.info("Professional report written: %s", output_path)
Loading