mbox series
Message ID20260604141826.2998337-2-robin@jarry.cc
StateNew
Delegate
ArchivedNo
Headers
show
Return-Path: <robin@jarry.cc>
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 <pw@patches.jarry.cc>; Thu, 04 Jun 2026 16:18:29 +0200 (CEST)
From: Robin Jarry <robin@jarry.cc>
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: <pw.jarry.cc>
Content-Transfer-Encoding: 8bit
Series
Implement forge bidirectional sync
github_branchpatchwork/implement-forge-bidirectional-sync-c
github_prhttps://github.com/rjarry/patchwork/pull/4

Commit Message

Robin JarryJun. 4, 2026, 16:18. UTC
[v3,01/16] parsemail: wrap parse_mail() in a single transaction

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 <robin@jarry.cc>
---
 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

Patch

mbox series
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.