Harden auth, AI tutor, payments, and subscriptions
This commit is contained in:
@@ -10,29 +10,12 @@ from django.utils import timezone
|
||||
|
||||
from statuslog.signals import status_changed
|
||||
|
||||
from .models import Node, NodeProgress, UserLessonCompletion
|
||||
from .models import NodeProgress, UserLessonCompletion
|
||||
|
||||
|
||||
def _get_or_create_progress(user_id, node):
|
||||
"""
|
||||
⚛️🔒 اصلاح race condition: قبلاً این تابع فقط get_or_create میکرد و
|
||||
ردیفِ برگرداندهشده (چه تازهساختهشده، چه از قبل موجود) هیچوقت قفل
|
||||
نمیشد. دو درخواستِ همزمان برای همون (user_id, node) — مثلاً دو پاسخِ
|
||||
پشتسرهم یا یک پاسخ و یک تکمیلِ درس که تقریباً همزمان میرسن —
|
||||
میتونستن هر دو همون ردیفِ NodeProgress رو با status/started_at/
|
||||
last_activity_at یکسان بخونن، هر دو مستقل مقدارهای جدید رو محاسبه
|
||||
کنن و save کنن؛ نتیجهی یکی از دو نویسش گم میشد (lost update) —
|
||||
مثلاً یک started_at یا یک تغییرِ status میتونست توسط نویسش دیگری
|
||||
که دیرتر commit میشه، بازنویسی/نادیده گرفته بشه.
|
||||
|
||||
حالا دقیقاً مثل الگوی grant_free_subscription در subscription/services.py:
|
||||
اول get_or_create برای اطمینان از وجودِ ردیف (idempotent، خودش با
|
||||
savepoint در برابر race روی خودِ create محافظتشده)، بعد همون ردیف با
|
||||
select_for_update دوباره خونده و قفل میشه تا خواندن/تغییر/ذخیرهی
|
||||
فیلدهاش توسط caller، در برابر concurrent update ایمن باشه. caller
|
||||
باید این تابع رو همیشه داخل transaction.atomic صدا بزنه (هر دو نقطهی
|
||||
فراخوانی در همین فایل همینطورن).
|
||||
"""
|
||||
"""get_or_create ایجاد میکند، سپس ردیف با select_for_update دوباره خوانده و قفل میشود
|
||||
تا خواندن/تغییر/ذخیره در برابر concurrent update ایمن باشد. باید داخل transaction.atomic صدا زده شود."""
|
||||
NodeProgress.objects.get_or_create(user_id=user_id, node=node)
|
||||
progress = NodeProgress.objects.select_for_update().get(user_id=user_id, node=node)
|
||||
return progress, False
|
||||
|
||||
Reference in New Issue
Block a user