fix: atomic counters, plan limit re-check, and double-submit guard
Backend: - Tree usage_count: use SQL-level UPDATE (Tree.usage_count + 1) instead of Python-level increment to prevent lost updates under concurrency - Tag usage_count: same SQL-level atomic increment/decrement in both create_tree and update_tree (delete_tree already used this pattern) - Plan tree limit: re-check count after db.flush() to close the TOCTOU window where two concurrent creates could both pass the pre-check Frontend: - TreeEditorPage: add isSaving early-return guard inside handleSaveDraft and handlePublish callbacks so Ctrl+S can't bypass the button disabled prop and fire duplicate save requests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from fastapi.responses import PlainTextResponse
|
||||
from pydantic import BaseModel, Field as PydanticField
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import select, update as sa_update
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.models.tree import Tree
|
||||
@@ -189,8 +189,10 @@ async def start_session(
|
||||
session_variables=session_variables,
|
||||
)
|
||||
|
||||
# Increment tree usage count
|
||||
tree.usage_count += 1
|
||||
# Atomically increment tree usage count (SQL-level to avoid lost updates)
|
||||
await db.execute(
|
||||
sa_update(Tree).where(Tree.id == tree.id).values(usage_count=Tree.usage_count + 1)
|
||||
)
|
||||
|
||||
db.add(new_session)
|
||||
await db.commit()
|
||||
|
||||
Reference in New Issue
Block a user