From fab92d914a1f856ff210558ba5c23acc2da499f9 Mon Sep 17 00:00:00 2001 From: Sahil Date: Mon, 23 Mar 2026 15:48:00 +0530 Subject: [PATCH 1/2] fix: resolve race condition in FeatureSegment priority assignment --- api/features/models.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/api/features/models.py b/api/features/models.py index 43ab7f986bbb..47aaa81ca8be 100644 --- a/api/features/models.py +++ b/api/features/models.py @@ -13,7 +13,7 @@ ObjectDoesNotExist, ValidationError, ) -from django.db import models +from django.db import models, transaction from django.db.models import Max, Q, QuerySet from django.utils import formats, timezone from django_lifecycle import ( # type: ignore[import-untyped] @@ -215,7 +215,9 @@ def _get_project(self) -> typing.Optional["Project"]: return self.project -def get_next_segment_priority(feature): # type: ignore[no-untyped-def] +def get_next_segment_priority(feature):# type: ignore[no-untyped-def] + Feature.objects.select_for_update().get(pk=feature.id) + feature_segments = FeatureSegment.objects.filter(feature=feature).order_by( "-priority" ) @@ -280,6 +282,13 @@ class FeatureSegment( order_with_respect_to = ("feature", "environment", "environment_feature_version") objects = FeatureSegmentManager() # type: ignore[misc] + @hook(BEFORE_CREATE) + def set_priority(self) -> None: + if self.priority is None: + # We use an atomic transaction here to ensure + # the select_for_update() lock in get_next_segment_priority works. + with transaction.atomic(): + self.priority = get_next_segment_priority(self.feature) class Meta: unique_together = ( From ab91d4913280e01615d0dc831362d4c310e7a258 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:30:04 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- api/features/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/features/models.py b/api/features/models.py index 47aaa81ca8be..afb7f69637dd 100644 --- a/api/features/models.py +++ b/api/features/models.py @@ -215,7 +215,7 @@ def _get_project(self) -> typing.Optional["Project"]: return self.project -def get_next_segment_priority(feature):# type: ignore[no-untyped-def] +def get_next_segment_priority(feature): # type: ignore[no-untyped-def] Feature.objects.select_for_update().get(pk=feature.id) feature_segments = FeatureSegment.objects.filter(feature=feature).order_by( @@ -282,10 +282,11 @@ class FeatureSegment( order_with_respect_to = ("feature", "environment", "environment_feature_version") objects = FeatureSegmentManager() # type: ignore[misc] + @hook(BEFORE_CREATE) def set_priority(self) -> None: if self.priority is None: - # We use an atomic transaction here to ensure + # We use an atomic transaction here to ensure # the select_for_update() lock in get_next_segment_priority works. with transaction.atomic(): self.priority = get_next_segment_priority(self.feature)