forked from cleveragents/cleveragents-core
c406781b86
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
127 lines
2.8 KiB
Python
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
|