From 8f3b242870dbb3355502845cbd05b5b2c790df4b Mon Sep 17 00:00:00 2001 From: mihf05 Date: Fri, 19 Dec 2025 22:50:33 +0600 Subject: [PATCH 1/2] feat: Implement atomic transaction for activating broadcast messages to prevent race conditions --- server/broadcast/api_views.py | 14 ++++++++++++-- server/broadcast/models.py | 14 +++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/server/broadcast/api_views.py b/server/broadcast/api_views.py index 891f397..1ef7455 100644 --- a/server/broadcast/api_views.py +++ b/server/broadcast/api_views.py @@ -2,6 +2,7 @@ from rest_framework.decorators import action, api_view, permission_classes from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated, AllowAny +from django.db import transaction from .models import BroadcastMessage from .serializers import BroadcastMessageSerializer from dashboard.models import UserDetails @@ -46,8 +47,17 @@ def set_active(self, request, pk=None): if message.user != request.user and not request.user.is_staff: return Response({'error': 'Permission denied'}, status=status.HTTP_403_FORBIDDEN) - message.active = True - message.save() + # Use atomic transaction with row locking to prevent race conditions + with transaction.atomic(): + # Lock and deactivate all user's messages + BroadcastMessage.objects.select_for_update().filter( + user=request.user, active=True + ).update(active=False) + + # Activate the selected message + message.active = True + message.save() + return Response({'message': 'Message set as active'}) diff --git a/server/broadcast/models.py b/server/broadcast/models.py index 0b5a1ee..45df14b 100644 --- a/server/broadcast/models.py +++ b/server/broadcast/models.py @@ -1,4 +1,4 @@ -from django.db import models +from django.db import models, transaction from django.conf import settings @@ -10,8 +10,16 @@ class BroadcastMessage(models.Model): def save(self, *args, **kwargs): if self.active: - BroadcastMessage.objects.filter(user=self.user, active=True).update(active=False) - super().save(*args, **kwargs) + # Use atomic transaction with row locking to prevent race conditions + with transaction.atomic(): + # Lock and deactivate all user's active messages + BroadcastMessage.objects.select_for_update().filter( + user=self.user, active=True + ).update(active=False) + # Save this message as active + super().save(*args, **kwargs) + else: + super().save(*args, **kwargs) def __str__(self): return f'{self.user.username}: {self.message[:20]}' \ No newline at end of file From d7f77f68a56984e4f88b679bb6947b6c730ca29a Mon Sep 17 00:00:00 2001 From: mihf05 Date: Fri, 19 Dec 2025 23:31:03 +0600 Subject: [PATCH 2/2] refactor: Optimize atomic transaction handling in BroadcastMessage model and viewset to prevent race conditions --- server/broadcast/api_views.py | 9 +++------ server/broadcast/models.py | 6 ++---- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/server/broadcast/api_views.py b/server/broadcast/api_views.py index 1ef7455..26d84b2 100644 --- a/server/broadcast/api_views.py +++ b/server/broadcast/api_views.py @@ -47,14 +47,13 @@ def set_active(self, request, pk=None): if message.user != request.user and not request.user.is_staff: return Response({'error': 'Permission denied'}, status=status.HTTP_403_FORBIDDEN) - # Use atomic transaction with row locking to prevent race conditions with transaction.atomic(): - # Lock and deactivate all user's messages + message = BroadcastMessage.objects.select_for_update().get(pk=pk) + BroadcastMessage.objects.select_for_update().filter( - user=request.user, active=True + user=message.user, active=True ).update(active=False) - # Activate the selected message message.active = True message.save() @@ -67,13 +66,11 @@ def get_user_broadcast(request, user_slug): try: user_details = UserDetails.objects.select_related('user').get(_slug=user_slug) - # Get active broadcast message if exists active_message = BroadcastMessage.objects.filter( user=user_details.user, active=True ).first() - # Build response with user details and active message response_data = { 'username': user_details.user.username, 'user_username': user_details.user.username, diff --git a/server/broadcast/models.py b/server/broadcast/models.py index 45df14b..24d36cb 100644 --- a/server/broadcast/models.py +++ b/server/broadcast/models.py @@ -2,7 +2,6 @@ from django.conf import settings -# Create your models here. class BroadcastMessage(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='messages') message = models.TextField() @@ -10,11 +9,10 @@ class BroadcastMessage(models.Model): def save(self, *args, **kwargs): if self.active: - # Use atomic transaction with row locking to prevent race conditions with transaction.atomic(): - # Lock and deactivate all user's active messages + BroadcastMessage.objects.select_for_update().filter( - user=self.user, active=True + user=self.user ).update(active=False) # Save this message as active super().save(*args, **kwargs)