模型视图集请求的竞争条件

问题描述 投票:0回答:1

我遇到了名为

projects/
的 Django Rest Framework ModelViewSet 端点的问题。我有一组请求(PATCH、DELETE、然后 GET)导致意外行为。请求和响应的时间表如下:

  1. 补丁请求于 14:45:09.420
  2. 在 14:45:12.724 删除请求 3.删除14:45:12.852的204回复
  3. PATCH 200 响应于 14:45:13.263
  4. 在 14:45:13.279 获取请求
  5. 在 14:45:13.714 获得 200 条回复 所有响应均表明成功。但是,DELETE 之后的 GET 响应包含假定已删除的模型。如果我稍后调用 GET 端点,则不再列出已删除的模型。

此行为表明存在潜在的竞争条件或缓存问题,其中 PATCH 操作在 DELETE 之后完成,或者 GET 请求返回不反映删除的缓存列表。

视图、序列化器和模型代码都非常普通:

class ProjectViewSet(ModelViewSet):
    parser_classes = (MultiPartParser, FormParser, JSONParser)
    queryset = Project.objects.all()
    serializer_class = ProjectSerializer
    pagination_class = ProjectPagination

class ProjectSerializer(serializers.ModelSerializer):
    creator = UserUUIDField(default=serializers.CurrentUserDefault())
    image = serializers.ImageField(required=False)

    class Meta:
        model = Project
        fields = "__all__"

class Project(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    creator = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    image = models.ForeignKey(
        wand_image,
        on_delete=models.DO_NOTHING,
        null=True,
        blank=True,
        related_name="projects"
    )

一个模型有对该模型的外键引用,但 on_delete 行为是将其设置为 null。

我正在使用 Google Cloud Run(一项无服务器后端服务)运行此程序

python django django-rest-framework race-condition
1个回答
0
投票

Rest Framework mixins 不是原子操作,因此有必要使用自定义 mixins 进行 UPDATE 和 DELETE 操作:

class AtomicDestroyModelMixin:
    """
    Destroy a model instance.
    """
    def destroy(self, request, *args, **kwargs):
        try:
            with transaction.atomic():
                instance = self.get_object()
                self.perform_destroy(instance)
        except OperationalError:
            raise DatabaseOperationException()

        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()


class AtomicUpdateModelMixin:
    """
    Update a model instance.
    """
    def update(self, request, *args, **kwargs):
        try:
            with transaction.atomic():
                partial = kwargs.pop("partial", False)
                instance = self.get_object()
                serializer = self.get_serializer(instance, data=request.data, partial=partial)
                serializer.is_valid(raise_exception=True)
                self.perform_update(serializer)

                if getattr(instance, "_prefetched_objects_cache", None):
                    instance._prefetched_objects_cache = {}
        except OperationalError:
            raise DatabaseOperationException()

        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs["partial"] = True
        return self.update(request, *args, **kwargs)
© www.soinside.com 2019 - 2024. All rights reserved.