From patchwork Thu Jun 4 14:18:10 2026 Return-Path: Received: from ringo (2a01cb00021ec0002e23edbec21b0e73.ipv6.abo.wanadoo.fr [IPv6:2a01:cb00:21e:c000:2e23:edbe:c21b:e73]) by patches.jarry.cc (Postfix) with ESMTP id 53DFC1BC434C for ; Thu, 04 Jun 2026 16:18:29 +0200 (CEST) From: Robin Jarry To: pw@patches.jarry.cc Subject: [PATCH v3 01/16] parsemail: wrap parse_mail() in a single transaction Date: Thu, 4 Jun 2026 16:18:10 +0200 Message-ID: <20260604141826.2998337-2-robin@jarry.cc> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260604141826.2998337-1-robin@jarry.cc> References: <20260604141826.2998337-1-robin@jarry.cc> MIME-Version: 1.0 List-ID: X-Patchwork-Submitter: Robin Jarry X-Patchwork-Id: 98 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Wrap the entire parse_mail() call in transaction.atomic() so that all database writes from email parsing run inside a single transaction. The existing transaction.atomic() blocks inside parse_mail() become savepoints within this outer transaction. The series deduplication retry logic continues to work since savepoint rollbacks are scoped to their own savepoint. This also ensures that any on_commit() callbacks registered by signal handlers only fire after the full email has been parsed and all patch/series associations are committed. Signed-off-by: Robin Jarry --- patchwork/management/commands/parsemail.py | 4 +++- .../notes/parsemail-transaction-d4e5f6g7h8i9j0k1.yaml | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/parsemail-transaction-d4e5f6g7h8i9j0k1.yaml diff --git a/patchwork/management/commands/parsemail.py b/patchwork/management/commands/parsemail.py index bcb257fe9714..2f90047a991b 100644 --- a/patchwork/management/commands/parsemail.py +++ b/patchwork/management/commands/parsemail.py @@ -8,6 +8,7 @@ import logging import sys from django.core.management import base +from django.db import transaction from patchwork.parser import parse_mail from patchwork.parser import DuplicateMailError @@ -57,7 +58,8 @@ class Command(base.BaseCommand): # broken email (ValueError): 1 (this could be noisy, if it's an issue # we could use a different return code) try: - result = parse_mail(mail, options['list_id']) + with transaction.atomic(): + result = parse_mail(mail, options['list_id']) if result is None: logger.warning('Nothing added to database') except DuplicateMailError as exc: diff --git a/releasenotes/notes/parsemail-transaction-d4e5f6g7h8i9j0k1.yaml b/releasenotes/notes/parsemail-transaction-d4e5f6g7h8i9j0k1.yaml new file mode 100644 index 000000000000..37ebb0573906 --- /dev/null +++ b/releasenotes/notes/parsemail-transaction-d4e5f6g7h8i9j0k1.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Wrap the entire parse_mail() function in a single database + transaction to prevent partial state from being visible to + concurrent readers.