Files
wehub-resource-sync 6ede33ccdb
Build and Push Docker Images / create_manifest (web, surfsense-web, , cpu) (push) Has been cancelled
Build and Push Docker Images / finalize_release (push) Has been cancelled
Obsidian Plugin Lint / lint (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64, , runner, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64, , runner, false, cpu) (push) Has been cancelled
Build and Push Docker Images / compute_version (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, , production, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, , production, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda126, production, true, cuda126) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda126, production, true, cuda126) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda, production, true, cuda) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda, production, true, cuda) (push) Has been cancelled
Build and Push Docker Images / verify_digests (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, , cpu) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda, cuda) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda126, cuda126) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:33:44 +08:00

62 lines
2.2 KiB
Python

"""The durable checkpointer survives Celery's fresh-loop-per-task model.
Slice 1's whole point: the shared ``AsyncPostgresSaver`` pool binds connections
to the loop that opened them, and Celery runs each task on a new loop. This
writes a checkpoint on one ``run_async_celery_task`` loop and reads it back on a
*fresh* one — proving the per-task pool dispose lets a new loop reopen and read
committed state, rather than stalling on a stale connection.
Uses the real pool against the real (test) Postgres, so a regression in the
dispose wiring fails here, not just in production.
"""
from __future__ import annotations
import uuid
import pytest
from langgraph.checkpoint.base import empty_checkpoint
from app.agents.chat.runtime.checkpointer import close_checkpointer, get_checkpointer
from app.tasks.celery_tasks import run_async_celery_task
pytestmark = pytest.mark.integration
def _config(thread_id: str) -> dict:
return {"configurable": {"thread_id": thread_id, "checkpoint_ns": ""}}
def test_checkpoint_written_on_one_loop_is_readable_on_a_fresh_loop() -> None:
thread_id = f"cross-loop-{uuid.uuid4()}"
config = _config(thread_id)
checkpoint = empty_checkpoint()
async def _write() -> None:
cp = await get_checkpointer()
await cp.aput(config, checkpoint, {"source": "update", "step": 0}, {})
async def _read():
cp = await get_checkpointer()
return await cp.aget_tuple(config)
async def _cleanup() -> None:
cp = await get_checkpointer()
delete = getattr(cp, "adelete_thread", None)
if delete is not None:
await delete(thread_id)
# Loop 1 writes and commits; run_async_celery_task disposes the pool after.
run_async_celery_task(_write)
# Loop 2 is a brand-new event loop: a stale loop-bound pool would stall
# here (PoolTimeout). It must reopen and read the committed checkpoint.
tup = run_async_celery_task(_read)
try:
assert tup is not None, "fresh loop could not read the prior checkpoint"
assert tup.checkpoint["id"] == checkpoint["id"]
finally:
run_async_celery_task(_cleanup)
run_async_celery_task(lambda: close_checkpointer())