fix(concurrency): make ensure_default_mock_actor atomic to prevent TOCTOU race
Merge the two separate database transactions in ensure_default_mock_actor() into a single atomic transaction to prevent Time-of-Check-Time-of-Use (TOCTOU) race conditions in concurrent environments (e.g., parallel test workers). The original implementation had a gap between checking for an existing default actor and creating a new mock actor. In concurrent scenarios, multiple threads could both observe no default and attempt to create one, leading to potential constraint violations or inconsistent state. The fix consolidates both operations into a single transaction, ensuring that the check and creation are atomic and safe for concurrent callers. Added BDD test scenarios to verify idempotency and concurrent safety: - Idempotency: calling ensure_default_mock_actor multiple times produces same result - Existing default safety: respects existing default actors - Single actor creation: ensures only one mock actor is created Closes #8448
This commit is contained in:
@@ -296,3 +296,30 @@ def after_scenario(context: Context, scenario) -> None:
|
||||
if hasattr(context, "_cleanup_handlers"):
|
||||
for handler in context._cleanup_handlers:
|
||||
handler()
|
||||
|
||||
|
||||
@when('I ensure the default mock actor with env value "{value}" again')
|
||||
def step_ensure_default_mock_actor_again(context: Context, value: str) -> None:
|
||||
os.environ["CLEVERAGENTS_TESTING_USE_MOCK_AI"] = value
|
||||
context.result_actor = context.actor_service.ensure_default_mock_actor()
|
||||
|
||||
|
||||
@then('only one "{name}" actor should exist')
|
||||
def step_only_one_actor_exists(context: Context, name: str) -> None:
|
||||
actors = context.actor_repo.list_all()
|
||||
count = sum(1 for actor in actors if actor.name == name)
|
||||
assert count == 1, f"Expected 1 actor named {name}, but found {count}"
|
||||
|
||||
|
||||
@then("only one default actor should exist")
|
||||
def step_only_one_default_actor(context: Context) -> None:
|
||||
actors = context.actor_repo.list_all()
|
||||
default_count = sum(1 for actor in actors if actor.is_default)
|
||||
assert default_count == 1, f"Expected 1 default actor, but found {default_count}"
|
||||
|
||||
|
||||
@then("exactly one actor should exist")
|
||||
def step_exactly_one_actor(context: Context) -> None:
|
||||
actors = context.actor_repo.list_all()
|
||||
count = len(actors)
|
||||
assert count == 1, f"Expected 1 actor, but found {count}"
|
||||
|
||||
Reference in New Issue
Block a user