Files
cleveragents-core/robot/helper_concurrency_locks.py
freemo 7a298ede6e
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 16s
CI / quality (pull_request) Successful in 30s
CI / build (pull_request) Successful in 24s
CI / security (pull_request) Successful in 40s
CI / typecheck (pull_request) Successful in 1m0s
CI / integration_tests (pull_request) Successful in 4m14s
CI / unit_tests (pull_request) Successful in 16m25s
CI / docker (pull_request) Successful in 55s
CI / benchmark-regression (pull_request) Successful in 22m55s
CI / coverage (pull_request) Successful in 38m4s
CI / lint (push) Successful in 13s
CI / build (push) Successful in 15s
CI / quality (push) Successful in 28s
CI / typecheck (push) Successful in 31s
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 31s
CI / integration_tests (push) Successful in 3m19s
CI / benchmark-publish (push) Successful in 14m23s
CI / unit_tests (push) Successful in 14m44s
CI / docker (push) Successful in 38s
CI / coverage (push) Successful in 32m22s
feat(concurrency): add plan and project locks
Implemented plan-level and project-level locking with configurable timeouts.
Added locks table via Alembic migration storing owner_id, resource_type,
resource_id, acquired_at, and expires_at. Locks enforced in
PlanLifecycleService transitions. Support for re-entrant acquisition,
lock renewal, graceful shutdown release, and startup cleanup of expired
locks. Added diagnostics check for stale lock reporting.

ISSUES CLOSED: #327
2026-02-25 10:48:05 -05:00

136 lines
3.7 KiB
Python

"""Helper script for concurrency_locks.robot smoke test."""
from __future__ import annotations
import sys
from datetime import UTC, datetime, timedelta
from typing import Any
sys.path.insert(0, "src")
sys.path.insert(0, ".")
from sqlalchemy import create_engine, event
from sqlalchemy.orm import Session, sessionmaker
from cleveragents.application.services.lock_service import LockService
from cleveragents.core.exceptions import LockConflictError, LockExpiredError
from cleveragents.infrastructure.database.models import Base, LockModel
def main() -> None:
engine = create_engine(
"sqlite:///:memory:",
echo=False,
future=True,
connect_args={"check_same_thread": False},
)
@event.listens_for(engine, "connect")
def _fk(dbapi_conn: Any, _rec: Any) -> None:
cursor = dbapi_conn.cursor()
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()
Base.metadata.create_all(engine)
sf: sessionmaker[Session] = sessionmaker(
bind=engine,
expire_on_commit=False,
autoflush=False,
autocommit=False,
class_=Session,
)
svc = LockService(session_factory=sf)
# Test 1: basic acquire
assert svc.acquire("owner-1", "plan", "p1") is True
print("PASS: acquire")
# Test 2: re-entrant
assert svc.acquire("owner-1", "plan", "p1") is True
print("PASS: re-entrant acquire")
# Test 3: conflict
try:
svc.acquire("owner-2", "plan", "p1")
print("FAIL: expected LockConflictError")
sys.exit(1)
except LockConflictError:
print("PASS: conflict detection")
# Test 4: release
assert svc.release("owner-1", "plan", "p1") is True
print("PASS: release")
# Test 5: acquire after release
assert svc.acquire("owner-2", "plan", "p1") is True
print("PASS: acquire after release")
# Test 6: renew
assert svc.renew("owner-2", "plan", "p1") is True
print("PASS: renew")
# Test 7: release_all_for_owner
svc.acquire("owner-3", "plan", "p10")
svc.acquire("owner-3", "project", "proj-1")
count = svc.release_all_for_owner("owner-3")
assert count == 2, f"Expected 2, got {count}"
print("PASS: release_all_for_owner")
# Test 8: cleanup_expired
now = datetime.now(tz=UTC)
expired = (now - timedelta(seconds=60)).isoformat()
acquired = (now - timedelta(seconds=120)).isoformat()
session = sf()
session.add(
LockModel(
owner_id="old",
resource_type="plan",
resource_id="stale-1",
acquired_at=acquired,
expires_at=expired,
)
)
session.commit()
session.close()
cleaned = svc.cleanup_expired()
assert cleaned >= 1, f"Expected >=1 cleaned, got {cleaned}"
print("PASS: cleanup_expired")
# Test 9: count_stale_locks returns 0 after cleanup
assert svc.count_stale_locks() == 0
print("PASS: count_stale_locks")
# Test 10: is_locked
svc.acquire("owner-4", "plan", "check-me")
assert svc.is_locked("plan", "check-me") is True
assert svc.is_locked("plan", "not-locked") is False
print("PASS: is_locked")
# Test 11: renew expired raises LockExpiredError
session2 = sf()
session2.add(
LockModel(
owner_id="owner-5",
resource_type="plan",
resource_id="expired-renew",
acquired_at=acquired,
expires_at=expired,
)
)
session2.commit()
session2.close()
try:
svc.renew("owner-5", "plan", "expired-renew")
print("FAIL: expected LockExpiredError")
sys.exit(1)
except LockExpiredError:
print("PASS: renew expired raises error")
print("\nPASS: concurrency_locks smoke test")
if __name__ == "__main__":
main()