You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ViewFlow自定义视图中原子事务未回滚的问题排查

ViewFlow流程启动时事务回滚失效问题

我自定义了流程创建视图,用于将登录用户的邮箱添加到流程对象中,同时添加了事务块,期望当self.activation_done()执行失败时,能回滚form_valid()中的所有数据库变更,不保存新流程实例并在表单上显示错误信息。

初始实现代码:

from django.core.exceptions import ValidationError
from django.db import transaction
from django.http import HttpResponseRedirect
from viewflow.flow.views import CreateProcessView, StartFlowMixin


class StarterEmailCreateProcessView(CreateProcessView):
    """
    为流程模型设置用户邮箱
    """
    def form_valid(self, form, *args, **kwargs):
        """表单验证通过时,保存关联模型并完成任务"""
        try:
            with transaction.atomic():
                super(StartFlowMixin, self).form_valid(form, *args, **kwargs)
                # 为流程实例设置提交者邮箱
                self.object.requester_email = self.request.user.email
                self.object.save()
                self.activation_done(form, *args, **kwargs)
        except Exception as ex:
            form.add_error(None, ValidationError(str(ex)))
            return self.form_invalid(form, *args, **kwargs)
        return HttpResponseRedirect(self.get_success_url())

按Django文档说明,事务内抛出异常会触发回滚,但实际测试时,表单显示错误信息的同时,新流程实例依然被保存,事务并未回滚。


更新1:
如果在self.activation_done之前主动抛出异常,事务可以正常回滚,完全搞不懂self.activation_done的内部逻辑为什么会阻止回滚?

测试代码:

class StarterEmailCreateProcessView(CreateProcessView):
    """
    为流程模型设置用户邮箱
    """
    def form_valid(self, form, *args, **kwargs):
        """表单验证通过时,保存关联模型并完成任务"""
        try:
            with transaction.atomic():
                super(StartFlowMixin, self).form_valid(form, *args, **kwargs)
                # 为流程实例设置提交者邮箱
                self.object.requester_email = self.request.user.email
                self.object.save()
                raise Exception('11111111111')
                self.activation_done(form, *args, **kwargs)
                
        except Exception as ex:
            form.add_error(None, ValidationError(str(ex)))
            return self.form_invalid(form, *args, **kwargs)
        return HttpResponseRedirect(self.get_success_url())

我还尝试了嵌套捕获异常的方式,虽然能捕获到异常,但事务仍然没有回滚:

def form_valid(self, form, *args, **kwargs):
        """表单验证通过时,保存关联模型并完成任务"""
        try:
            with transaction.atomic():
                super(StartFlowMixin, self).form_valid(form, *args, **kwargs)
                # 为流程实例设置提交者邮箱
                self.object.requester_email = self.request.user.email
                self.object.save()
                try:
                    self.activation_done(form, *args, **kwargs)
                except:
                    raise Exception(1111111)
        except Exception as ex:
            form.add_error(None, ValidationError(str(ex)))
            return self.form_invalid(form, *args, **kwargs)
        return HttpResponseRedirect(self.get_success_url())

更新2:
查看activation.done的源码后,我注释掉其中的锁逻辑,事务就能正常回滚了:

@Activation.status.transition(source=STATUS.PREPARED, target=STATUS.DONE)
    def done(self):
        """
        创建并启动新的流程实例。

        .. seealso::
            :data:`viewflow.signals.task_started`

        .. seealso::
             :data:`viewflow.signals.task_finished`

        .. seealso::
            :data:`viewflow.signals.flow_started`

        """
        with transaction.atomic(savepoint=True):
            signals.task_started.send(sender=self.flow_class, process=self.process, task=self.task)

            self.process.save()

            # lock_impl = self.flow_class.lock_impl(self.flow_class.instance)
            # self.lock = lock_impl(self.flow_class, self.process.pk)
            # self.lock.__enter__()

            self.task.process = self.process
            self.task.finished = now()
            self.task.save()

            signals.task_finished.send(sender=self.flow_class, process=self.process, task=self.task)
            signals.flow_started.send(sender=self.flow_class, process=self.process, task=self.task)

            self.activate_next()

对应的锁实现代码如下,目前还没弄明白它阻止事务回滚的机制:

class NoLock(object):
    """
    无悲观锁,仅在事务中执行流程任务。
    不适用于包含Join节点的流程。
    """

    def __call__(self, flow):
        @contextmanager
        def lock(flow_class, process_pk):
            with transaction.atomic():
                yield
        return lock

no_lock = NoLock()

内容的提问来源于stack exchange,提问作者James Lin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 16:42:02