Enforce the Devii task quotas with atomic reservations
The creation and run quotas were checked and then acted on, so two concurrent create_task calls or two schedulers could both pass the check and overshoot the limit. Both are now a single conditional INSERT decided on the driver rowcount: reserve_run takes a run slot after the claim and releases the claim by deferring when the quota is spent, and insert_task_within_quota does the same for the task row itself. Racing twelve and sixteen processes now yields exactly the limit. The atomic insert names its columns, and dataset skips a None valued key when it creates a table lazily, so the store declares the full task column set up front. Both the column and index ensures now tolerate a concurrent duplicate, since several processes build a store at once and SQLite DDL is not idempotent. Adds the quota, task-run context, guard, store and scheduler test suites, and documents the chokepoints and the unhackable task-run flag.
This commit is contained in:
@@ -17,6 +17,8 @@ from devplacepy.services.devii.tasks.schedule import now_utc, to_iso
|
||||
from devplacepy.services.devii.tasks.store import TaskStore, claim, due_rows
|
||||
from devplacepy.utils import generate_uid
|
||||
|
||||
SIGNUP_AFTER_PRIMARY_ADMIN = "2099-01-01T00:00:00"
|
||||
|
||||
|
||||
def _account(local_db, role):
|
||||
uid = generate_uid()
|
||||
@@ -26,7 +28,7 @@ def _account(local_db, role):
|
||||
"username": f"store-{uid[-10:]}",
|
||||
"role": role,
|
||||
"deleted_at": None,
|
||||
"created_at": to_iso(now_utc()),
|
||||
"created_at": SIGNUP_AFTER_PRIMARY_ADMIN,
|
||||
}
|
||||
)
|
||||
invalidate_admins_cache()
|
||||
@@ -180,7 +182,8 @@ def test_claim_refuses_a_disabled_task(local_db):
|
||||
|
||||
|
||||
def test_due_rows_skips_future_and_running_tasks(local_db):
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Admin"))
|
||||
admin = _account(local_db, "Admin")
|
||||
store = TaskStore(local_db, "user", admin)
|
||||
now = now_utc()
|
||||
due = _record(next_run_at=to_iso(now.replace(year=now.year - 1)))
|
||||
future = _record(next_run_at=to_iso(now.replace(year=now.year + 1)))
|
||||
@@ -238,13 +241,13 @@ def test_controller_refuses_nested_creation_for_a_member(local_db):
|
||||
|
||||
|
||||
def test_controller_refuses_nested_run_now_for_a_member(local_db):
|
||||
import json
|
||||
|
||||
store = TaskStore(local_db, "user", _account(local_db, "Member"))
|
||||
controller = TaskController(store)
|
||||
created = controller.create_task(
|
||||
{"prompt": "work", "kind": "interval", "every_seconds": 900}
|
||||
)
|
||||
import json
|
||||
|
||||
uid = json.loads(created)["task"]["uid"]
|
||||
with task_run_scope():
|
||||
with pytest.raises(ToolInputError) as failure:
|
||||
|
||||
Reference in New Issue
Block a user