Files
cleveragents-core/benchmarks/security_async_cleanup_bench.py
T
khyari hamza c406781b86
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / quality (pull_request) Successful in 17s
CI / build (pull_request) Successful in 20s
CI / typecheck (pull_request) Successful in 41s
CI / security (pull_request) Successful in 57s
CI / integration_tests (pull_request) Successful in 2m53s
CI / unit_tests (pull_request) Successful in 19m17s
CI / docker (pull_request) Successful in 1m1s
CI / benchmark-regression (pull_request) Successful in 26m11s
CI / coverage (pull_request) Successful in 47m20s
fix(security): address PR #435 review feedback
P0: reject register() after close_all() with RuntimeError.
P1: catch CancelledError in close_all(), use WeakKeyDictionary for
cancellation_reasons to prevent memory leak, guard StateManager
update_state/reset/load_checkpoint/time_travel after close().
P2: contextlib.suppress in __del__ for partial construction, re-cancel
pending tasks in cleanup_tasks_async, handle late tasks added during
await window, guard AcpEventQueue.publish() after close with _is_closed
flag and is_closed property, fix ASV TimeRegisterBatch crash.
Tests: 5 new Behave scenarios (T1-T4 + is_closed), log handler and
event loop cleanup in after_scenario (T5-T6).
Docs: async_safety.md updated for register-after-close and state
mutation guards.

ISSUES CLOSED: #321
2026-02-26 14:30:43 +00:00

127 lines
2.8 KiB
Python

"""ASV benchmarks for async resource cleanup overhead (#321).
Measures registration, close_all, and leak-warning latency to establish
baselines for the AsyncResourceTracker.
"""
from __future__ import annotations
import asyncio
from cleveragents.core.async_cleanup import AsyncResourceTracker
class _FakeResource:
"""Minimal async resource for benchmarking."""
def __init__(self) -> None:
self.closed = False
async def close(self) -> None:
self.closed = True
def _make_tracker(count: int) -> AsyncResourceTracker:
"""Create a tracker pre-loaded with *count* fake resources."""
tracker = AsyncResourceTracker()
for i in range(count):
tracker.register(f"res-{i}", _FakeResource())
return tracker
class TimeRegisterSingle:
"""Benchmark registering a single resource."""
timeout = 10
def setup(self) -> None:
self.tracker = AsyncResourceTracker()
self.counter = 0
def teardown(self) -> None:
pass
def time_register_one(self) -> None:
name = f"bench-{self.counter}"
self.counter += 1
self.tracker.register(name, _FakeResource())
class TimeRegisterBatch:
"""Benchmark registering 100 resources in sequence."""
timeout = 10
number = 1 # Tracker cannot re-register names; recreate each iteration.
def setup(self) -> None:
self.tracker = AsyncResourceTracker()
def teardown(self) -> None:
pass
def time_register_100(self) -> None:
for i in range(100):
self.tracker.register(f"batch-{i}", _FakeResource())
class TimeCloseAll:
"""Benchmark close_all on pre-loaded trackers."""
timeout = 30
number = 1 # Re-create tracker for each iteration.
def setup(self) -> None:
self.tracker = _make_tracker(50)
def teardown(self) -> None:
pass
def time_close_50_resources(self) -> None:
asyncio.run(self.tracker.close_all())
class TimeCloseAllLarge:
"""Benchmark close_all with 500 resources."""
timeout = 60
number = 1
def setup(self) -> None:
self.tracker = _make_tracker(500)
def teardown(self) -> None:
pass
def time_close_500_resources(self) -> None:
asyncio.run(self.tracker.close_all())
class TimeLeakWarning:
"""Benchmark the _warn_unclosed finalizer path."""
timeout = 10
def setup(self) -> None:
self.tracker = _make_tracker(20)
def teardown(self) -> None:
pass
def time_warn_20_unclosed(self) -> None:
self.tracker._warn_unclosed()
class TimeOpenCount:
"""Benchmark the open_count property."""
timeout = 10
def setup(self) -> None:
self.tracker = _make_tracker(100)
def teardown(self) -> None:
pass
def time_open_count(self) -> None:
_ = self.tracker.open_count