-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
…ailer. 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
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 @@ def tpc_begin(self, transaction, subtransaction=False): | |
assert not subtransaction | ||
|
||
def tpc_vote(self, transaction): | ||
pass | ||
if self.vote is not None: | ||
return self.vote(*self.args) | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
mgedmin
Member
|
||
|
||
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 @@ def __init__(self, mailer): | |
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): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,17 +37,38 @@ def __init__(self, hostname='localhost', port=25, | |
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)) | ||
This comment has been minimized.
Sorry, something went wrong.
mgedmin
Member
|
||
|
||
# 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') | ||
|
Sending mail during tpc_vote is the right thing to do, but you need to ensure that the MailDataManager sortKey() sorts last or mail may be sent before another data manager raises an exception in tpc_vote and the transaction is retried. See the 1PC variant of the zope.sqlalchemy DataManager: https://github.com/zopefoundation/zope.sqlalchemy/blob/2c5d7a844831ba9ad00cfb39f4d4016bc81c580c/src/zope/sqlalchemy/datamanager.py#L109