Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@
## 2024-06-28 - Optimize array lookups in high-frequency React event handlers
**Learning:** Even if data is pre-processed/memoized (like cached `parsedTimestamps`), performing an O(N) linear search on that array inside a high-frequency event handler (like `<video onTimeUpdate>`, which fires multiple times per second) can cause UI stuttering for large arrays.
**Action:** When searching sorted arrays (like ordered video timestamps) in high-frequency event loops, replace linear iteration loops with an O(log N) binary search to minimize main thread blocking and ensure smooth UI execution.

## 2024-07-10 - Prevent event loop blocking in async routes
**Learning:** In the FastAPI backend, executing CPU-bound tasks (like `bcrypt` hashing) or using synchronous blocking functions (like `time.sleep()`) directly in `async def` route handlers blocks the event loop, severely degrading concurrent request handling performance.
**Action:** Never use `time.sleep()` in async routes (use `await asyncio.sleep()` instead). Offload CPU-bound tasks (like password hashing/verification) to a separate thread pool using `await starlette.concurrency.run_in_threadpool(func, *args)` to ensure the main event loop remains unblocked and responsive.
15 changes: 10 additions & 5 deletions backend/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import Optional
from fastapi import Depends, HTTPException, status, Request
from fastapi.security import OAuth2PasswordBearer
from starlette.concurrency import run_in_threadpool
import bcrypt
import jwt
from sqlalchemy.ext.asyncio import AsyncSession
Expand All @@ -22,11 +23,15 @@

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")

def verify_password(plain_password, hashed_password):
return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8'))
async def verify_password(plain_password, hashed_password):
def _verify():
return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8'))
return await run_in_threadpool(_verify)

def get_password_hash(password):
return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
async def get_password_hash(password):
def _hash():
return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
return await run_in_threadpool(_hash)

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
import uuid
Expand Down Expand Up @@ -65,7 +70,7 @@ async def initialize_admin_from_env(db: AsyncSession):
if admin_password_hash:
hashed = admin_password_hash
elif admin_password:
hashed = get_password_hash(admin_password)
hashed = await get_password_hash(admin_password)
else:
return False

Expand Down
8 changes: 4 additions & 4 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ async def register(user: UserCreate, db: AsyncSession = Depends(get_db)):
user_count = result.scalar()

is_first_user = user_count == 0
hashed_password = get_password_hash(user.password)
hashed_password = await get_password_hash(user.password)
db_user = User(
email=user.email,
hashed_password=hashed_password,
Expand All @@ -267,7 +267,7 @@ async def register(user: UserCreate, db: AsyncSession = Depends(get_db)):
@app.post("/api/auth/login")
async def login(user: UserLogin, db: AsyncSession = Depends(get_db)):
db_user = await get_user_by_email(db, user.email)
if not db_user or not verify_password(user.password, db_user.hashed_password):
if not db_user or not await verify_password(user.password, db_user.hashed_password):
raise HTTPException(status_code=401, detail="Incorrect email or password")

if not db_user.is_approved:
Expand Down Expand Up @@ -320,7 +320,7 @@ async def setup_initial_admin(user: UserCreate, db: AsyncSession = Depends(get_d
if not user.email or not user.password:
raise HTTPException(status_code=400, detail="Email and password required")

hashed_password = get_password_hash(user.password)
hashed_password = await get_password_hash(user.password)
admin_user = User(
email=user.email,
hashed_password=hashed_password,
Expand Down Expand Up @@ -859,7 +859,7 @@ async def get_frame_image(directory_path: str, frame_index: int, project: Projec
max_wait = 30
waited = 0
while not file_path.exists() and waited < max_wait:
time.sleep(1)
await asyncio.sleep(1)
waited += 1

if file_path.exists():
Expand Down
Loading