[Checkins] SVN: zope.sendmail/trunk/ Add a vote method the the mailer interface and implement it for SMTPMailer. Attempts to create the connection and HELO while in transaction vote rather than finish, so exceptions in making the connection will abort the current transaction safely.

Matthew Wilkes matthew at matthewwilkes.co.uk
Tue Jun 29 07:56:27 EDT 2010


Log message for revision 113972:
  Add a vote method the the mailer interface and implement it for SMTPMailer.  Attempts to create the connection and HELO while in transaction vote rather than finish, so exceptions in making the connection will abort the current transaction safely.
  
  The vote method needs to be provided to the MailDataManager, but if a Mailer implementation doesn't provide one DirectMailDelivery will provide a noop replacement and make a deprecation warning.
  
  Bump version to 3.8 branch

Changed:
  U   zope.sendmail/trunk/CHANGES.txt
  U   zope.sendmail/trunk/setup.py
  U   zope.sendmail/trunk/src/zope/sendmail/delivery.py
  U   zope.sendmail/trunk/src/zope/sendmail/interfaces.py
  U   zope.sendmail/trunk/src/zope/sendmail/mailer.py

-=-
Modified: zope.sendmail/trunk/CHANGES.txt
===================================================================
--- zope.sendmail/trunk/CHANGES.txt	2010-06-29 11:39:20 UTC (rev 113971)
+++ zope.sendmail/trunk/CHANGES.txt	2010-06-29 11:56:27 UTC (rev 113972)
@@ -2,10 +2,14 @@
 CHANGES
 =======
 
-3.7.3 (unreleased)
+3.8.0 (unreleased)
 ------------------
 
+- Add a vote method to Mailer implementations to allow them to abort a
+  transaction if it is known to be unsafe.
 
+- Prevent fatal errors in mail delivery causing potential database corruption.
+
 3.7.2 (2010-04-30)
 ------------------
 

Modified: zope.sendmail/trunk/setup.py
===================================================================
--- zope.sendmail/trunk/setup.py	2010-06-29 11:39:20 UTC (rev 113971)
+++ zope.sendmail/trunk/setup.py	2010-06-29 11:56:27 UTC (rev 113972)
@@ -22,7 +22,7 @@
 
 
 setup(name='zope.sendmail',
-      version = '3.7.3dev',
+      version = '3.8.0dev',
       url='http://pypi.python.org/pypi/zope.sendmail',
       license='ZPL 2.1',
       description='Zope sendmail',

Modified: zope.sendmail/trunk/src/zope/sendmail/delivery.py
===================================================================
--- zope.sendmail/trunk/src/zope/sendmail/delivery.py	2010-06-29 11:39:20 UTC (rev 113971)
+++ zope.sendmail/trunk/src/zope/sendmail/delivery.py	2010-06-29 11:56:27 UTC (rev 113972)
@@ -19,6 +19,8 @@
 
 import os
 import rfc822
+import logging
+import warnings
 from cStringIO import StringIO
 from random import randrange
 from time import strftime
@@ -34,12 +36,15 @@
 # zope.sendmail which defined QueueProcessorThread in this module
 from zope.sendmail.queue import QueueProcessorThread
 
+log = logging.getLogger("MailDataManager")
+
 class MailDataManager(object):
     implements(IDataManager)
 
-    def __init__(self, callable, args=(), onAbort=None):
+    def __init__(self, callable, args=(), vote=None, onAbort=None):
         self.callable = callable
         self.args = args
+        self.vote = vote
         self.onAbort = onAbort
         # Use the default thread transaction manager.
         self.transaction_manager = transaction.manager
@@ -69,10 +74,18 @@
         assert not subtransaction
 
     def tpc_vote(self, transaction):
-        pass
+        if self.vote is not None:
+            return self.vote(*self.args)
 
     def tpc_finish(self, transaction):
-        self.callable(*self.args)
+        try:
+            self.callable(*self.args)
+        except Exception, e:
+            # Any exceptions here can cause database corruption.
+            # Better to protect the data and potentially miss emails than
+            # leave a database in an inconsistent state which requires a
+            # guru to fix.
+            log.exception(e)
 
     tpc_abort = abort
 
@@ -111,8 +124,20 @@
         self.mailer = mailer
 
     def createDataManager(self, fromaddr, toaddrs, message):
+        try:
+            vote = self.mailer.vote
+        except AttributeError:
+            # We've got an old mailer, just pass through voting
+            warnings.warn("The mailer %s does not provide a vote method" 
+                                    % (repr(self.mailer)), DeprecationWarning)
+            
+            def vote(*args, **kwargs):
+                pass
+            
         return MailDataManager(self.mailer.send,
-                               args=(fromaddr, toaddrs, message))
+                               args=(fromaddr, toaddrs, message),
+                               vote=vote,
+                               onAbort=self.mailer.abort)
 
 
 class QueuedMailDelivery(AbstractMailDelivery):

Modified: zope.sendmail/trunk/src/zope/sendmail/interfaces.py
===================================================================
--- zope.sendmail/trunk/src/zope/sendmail/interfaces.py	2010-06-29 11:39:20 UTC (rev 113971)
+++ zope.sendmail/trunk/src/zope/sendmail/interfaces.py	2010-06-29 11:56:27 UTC (rev 113972)
@@ -136,11 +136,18 @@
         2822.  It should contain at least Date, From, To, and Message-Id
         headers.
 
-        Messages are sent immediatelly.
+        Messages are sent immediately.
 
         Dispatches an `IMailSentEvent` on successful delivery, otherwise an
         `IMailErrorEvent`.
         """
+    
+    def abort():
+        """Abort sending the message for asynchronous subclasses."""
+    
+    def vote(fromaddr, toaddrs, message):
+        """Raise an exception if there is a known reason why the message 
+        cannot be sent."""
 
 
 class ISMTPMailer(IMailer):

Modified: zope.sendmail/trunk/src/zope/sendmail/mailer.py
===================================================================
--- zope.sendmail/trunk/src/zope/sendmail/mailer.py	2010-06-29 11:39:20 UTC (rev 113971)
+++ zope.sendmail/trunk/src/zope/sendmail/mailer.py	2010-06-29 11:56:27 UTC (rev 113972)
@@ -37,18 +37,39 @@
         self.password = password
         self.force_tls = force_tls
         self.no_tls = no_tls
+        self.connection = None
 
-    def send(self, fromaddr, toaddrs, message):
-        connection = self.smtp(self.hostname, str(self.port))
+    def vote(self, fromaddr, toaddrs, message):
+        self.connection = self.smtp(self.hostname, str(self.port))
 
-        # send EHLO
-        code, response = connection.ehlo()
+        code, response = self.connection.ehlo()
         if code < 200 or code >= 300:
-            code, response = connection.helo()
+            code, response = self.connection.helo()
             if code < 200 or code >= 300:
                 raise RuntimeError('Error sending HELO to the SMTP server '
                                    '(code=%s, response=%s)' % (code, response))
+        
+        self.code, self.response = code, response
 
+
+    def abort(self):
+        if self.connection is None:
+            return
+        
+        try:
+            self.connection.quit()
+        except socket.sslerror:
+            #something weird happened while quiting
+            self.connection.close()
+
+    def send(self, fromaddr, toaddrs, message):
+        connection = getattr(self, 'connection', None)
+        if connection is None:
+            self.vote(fromaddr, toaddrs, message)
+
+        connection, code, response = self.connection, self.code, self.response
+            
+
         # encryption support
         have_tls =  connection.has_extn('starttls')
         if not have_tls and self.force_tls:



More information about the checkins mailing list