[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