Mark Sapiro pushed to branch master at GNU Mailman / Mailman Core


Commits:
5e77179e by Mark Sapiro at 2021-04-17T22:21:31-07:00
Handle attempt to get a message from the message store with a missing file.

- - - - -
d711ba42 by Mark Sapiro at 2021-04-18T05:43:43+00:00
Merge branch 'task' into 'master'

Handle attempt to get a message from the message store with a missing file.

Closes #877

See merge request mailman/mailman!836
- - - - -


4 changed files:

- src/mailman/docs/NEWS.rst
- src/mailman/model/messagestore.py
- src/mailman/model/tests/test_messagestore.py
- src/mailman/runners/task.py


Changes:

=====================================
src/mailman/docs/NEWS.rst
=====================================
@@ -36,6 +36,8 @@ Bugs
 * Pending probe bounce tokens now have a lifetime of 10 days.  (Closes #869)
 * Improve the performance of ``/users`` API when paginating by doing the
   pagination in database layer. (Closes #876)
+* Attempts to get a message from the message store with a missing file are
+  now handled.  (Closes #877)
 
 Command line
 ------------


=====================================
src/mailman/model/messagestore.py
=====================================
@@ -21,6 +21,7 @@ import os
 import errno
 import pickle
 
+from contextlib import suppress
 from mailman.config import config
 from mailman.database.transaction import dbconnection
 from mailman.interfaces.messages import IMessageStore
@@ -90,10 +91,16 @@ class MessageStore:
             makedirs(os.path.dirname(path))
         return hash32
 
-    def _get_message(self, row):
+    @dbconnection
+    def _get_message(self, store, row):
         path = os.path.join(config.MESSAGES_DIR, row.path)
-        with open(path, 'rb') as fp:
-            return pickle.load(fp)
+        # The message file may have been externally removed.
+        with suppress(FileNotFoundError):
+            with open(path, 'rb') as fp:
+                return pickle.load(fp)
+        # The message is gone.  Delete the entry and return None.
+        store.delete(row)
+        return None
 
     @dbconnection
     def get_message_by_id(self, store, message_id):


=====================================
src/mailman/model/tests/test_messagestore.py
=====================================
@@ -95,6 +95,25 @@ Message-ID: <ant>
         self._store.delete_message('<ant>')
         self.assertEqual(len(list(self._store.messages)), 0)
 
+    def test_get_message_missing_returns_none_and_deletes(self):
+        # Getting a message which is in the store, but which doesn't appear
+        # on the file system returns None and removes the message from the
+        # store.
+        msg = mfs("""\
+Message-ID: <ant>
+
+""")
+        self._store.add(msg)
+        self.assertEqual(len(list(self._store.messages)), 1)
+        # We have to use the SQLAlchemy API because the .get_message_by_*()
+        # methods return email Message objects, not IMessages.  The former
+        # does not give us the path to the object on the file system.
+        row = config.db.store.query(Message).filter_by(
+            message_id='<ant>').first()
+        os.remove(os.path.join(config.MESSAGES_DIR, row.path))
+        self.assertIsNone(self._store.get_message_by_id('<ant>'))
+        self.assertEqual(len(list(self._store.messages)), 0)
+
     def test_message_id_hash(self):
         # The new specification calls for a Message-ID-Hash header,
         # specifically without the X- prefix.


=====================================
src/mailman/runners/task.py
=====================================
@@ -100,10 +100,12 @@ class TaskRunner(Runner):
         count = 0
         messages = getUtility(IMessageStore)
         for msg in messages.messages:
-            mid = msg.get('message-id')
-            if mid not in mids:
-                messages.delete_message(mid)
-                count += 1
+            # msg can be None if file is already removed.
+            if msg is not None:
+                mid = msg.get('message-id')
+                if mid not in mids:
+                    messages.delete_message(mid)
+                    count += 1
         tlog.info('Task runner deleted %d orphaned messages', count)
 
     def _evict_cache(self):



View it on GitLab: 
https://gitlab.com/mailman/mailman/-/compare/b2c30ac2e9ed39274a7c9603d96ce28c50bab868...d711ba42c1d0dfb3aed48f923d69853275c652d2

-- 
View it on GitLab: 
https://gitlab.com/mailman/mailman/-/compare/b2c30ac2e9ed39274a7c9603d96ce28c50bab868...d711ba42c1d0dfb3aed48f923d69853275c652d2
You're receiving this email because of your account on gitlab.com.


_______________________________________________
Mailman-checkins mailing list -- mailman-checkins@python.org
To unsubscribe send an email to mailman-checkins-le...@python.org
https://mail.python.org/mailman3/lists/mailman-checkins.python.org/
Member address: arch...@jab.org

Reply via email to