summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Lib/mailbox.py18
-rw-r--r--Lib/test/test_mailbox.py29
-rw-r--r--Misc/NEWS4
3 files changed, 46 insertions, 5 deletions
diff --git a/Lib/mailbox.py b/Lib/mailbox.py
index d86ad94478..39968c6dd0 100644
--- a/Lib/mailbox.py
+++ b/Lib/mailbox.py
@@ -587,16 +587,19 @@ class _singlefileMailbox(Mailbox):
self._file = f
self._toc = None
self._next_key = 0
- self._pending = False # No changes require rewriting the file.
+ self._pending = False # No changes require rewriting the file.
+ self._pending_sync = False # No need to sync the file
self._locked = False
- self._file_length = None # Used to record mailbox size
+ self._file_length = None # Used to record mailbox size
def add(self, message):
"""Add message and return assigned key."""
self._lookup()
self._toc[self._next_key] = self._append_message(message)
self._next_key += 1
- self._pending = True
+ # _append_message appends the message to the mailbox file. We
+ # don't need a full rewrite + rename, sync is enough.
+ self._pending_sync = True
return self._next_key - 1
def remove(self, key):
@@ -642,6 +645,11 @@ class _singlefileMailbox(Mailbox):
def flush(self):
"""Write any pending changes to disk."""
if not self._pending:
+ if self._pending_sync:
+ # Messages have only been added, so syncing the file
+ # is enough.
+ _sync_flush(self._file)
+ self._pending_sync = False
return
# In order to be writing anything out at all, self._toc must
@@ -695,6 +703,7 @@ class _singlefileMailbox(Mailbox):
self._file = open(self._path, 'rb+')
self._toc = new_toc
self._pending = False
+ self._pending_sync = False
if self._locked:
_lock_file(self._file, dotlock=False)
@@ -731,6 +740,9 @@ class _singlefileMailbox(Mailbox):
"""Append message to mailbox and return (start, stop) offsets."""
self._file.seek(0, 2)
before = self._file.tell()
+ if len(self._toc) == 0:
+ # This is the first message
+ self._pre_mailbox_hook(self._file)
try:
self._pre_message_hook(self._file)
offsets = self._install_message(message)
diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py
index cb54505d9a..c56002bebf 100644
--- a/Lib/test/test_mailbox.py
+++ b/Lib/test/test_mailbox.py
@@ -945,7 +945,32 @@ class TestMaildir(TestMailbox, unittest.TestCase):
self._box._refresh()
self.assertTrue(refreshed())
-class _TestMboxMMDF(TestMailbox):
+
+class _TestSingleFile(TestMailbox):
+ '''Common tests for single-file mailboxes'''
+
+ def test_add_doesnt_rewrite(self):
+ # When only adding messages, flush() should not rewrite the
+ # mailbox file. See issue #9559.
+
+ # Inode number changes if the contents are written to another
+ # file which is then renamed over the original file. So we
+ # must check that the inode number doesn't change.
+ inode_before = os.stat(self._path).st_ino
+
+ self._box.add(self._template % 0)
+ self._box.flush()
+
+ inode_after = os.stat(self._path).st_ino
+ self.assertEqual(inode_before, inode_after)
+
+ # Make sure the message was really added
+ self._box.close()
+ self._box = self._factory(self._path)
+ self.assertEqual(len(self._box), 1)
+
+
+class _TestMboxMMDF(_TestSingleFile):
def tearDown(self):
super().tearDown()
@@ -1220,7 +1245,7 @@ class TestMH(TestMailbox, unittest.TestCase):
return os.path.join(self._path, '.mh_sequences.lock')
-class TestBabyl(TestMailbox, unittest.TestCase):
+class TestBabyl(_TestSingleFile, unittest.TestCase):
_factory = lambda self, path, factory=None: mailbox.Babyl(path, factory)
diff --git a/Misc/NEWS b/Misc/NEWS
index 20a24071d8..4dcbfc2efc 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -14,6 +14,10 @@ Core and Builtins
Library
-------
+- Issue #9559: If messages were only added, a new file is no longer
+ created and renamed over the old file when flush() is called on an
+ mbox, MMDF or Babyl mailbox.
+
- Issue 10924: Fixed mksalt() to use a RNG that is suitable for cryptographic
purpose.