diff options
Diffstat (limited to 'Mailman/Handlers/HandlerAPI.py')
| -rw-r--r-- | Mailman/Handlers/HandlerAPI.py | 136 |
1 files changed, 102 insertions, 34 deletions
diff --git a/Mailman/Handlers/HandlerAPI.py b/Mailman/Handlers/HandlerAPI.py index f098929fd..aba0e1a09 100644 --- a/Mailman/Handlers/HandlerAPI.py +++ b/Mailman/Handlers/HandlerAPI.py @@ -16,57 +16,122 @@ """Contains all the common functionality for the msg handler API.""" +import traceback +import time + from Mailman import mm_cfg from Mailman import Errors +from Mailman.pythonlib.StringIO import StringIO + + +# Exception classes for this subsystem. class HandlerError(Errors.MailmanError): """Base class for all handler errors.""" - pass class MessageHeld(HandlerError): """Base class for all message-being-held short circuits.""" - pass + def __str__(self): + return self.__class__.__doc__ + +class DiscardMessage(HandlerError): + """The message can be discarded with no further action""" + +class SomeRecipientsFailed(HandlerError): + """Delivery to some or all recipients failed""" + + + +# All messages which are delivered to the entire list membership go through +# this pipeline of handler modules. +LIST_PIPELINE = ['SpamDetect', + 'Approve', + 'Replybot', + 'Hold', + 'Cleanse', + 'CookHeaders', + 'ToDigest', + 'ToArchive', + 'ToUsenet', + 'CalcRecips', + 'Decorate', + mm_cfg.DELIVERY_MODULE, + 'AfterDelivery', + 'Acknowledge', + ] -def pipeline_delivery(mlist, msg, pipeline): - for modname in pipeline: - mod = __import__('Mailman.Handlers.'+modname) +# Central mail delivery handler +def DeliverToList(mlist, msg, msgdata): + pipeline = msgdata.get('pipeline', LIST_PIPELINE) + while pipeline: + modname = pipeline.pop(0) + mod = __import__('Mailman.Handlers.' + modname) func = getattr(getattr(getattr(mod, 'Handlers'), modname), 'process') try: - func(mlist, msg) + mlist.LogMsg('debug', 'starting ' + modname) + func(mlist, msg, msgdata) + mlist.LogMsg('debug', 'done with ' + modname) + except DiscardMessage: + # Throw the message away; we need do nothing else with it. + return 0 except MessageHeld: + # Let the approval process take it from here. The message no + # longer needs to be queued. + return 0 + except SomeRecipientsFailed: + # The delivery module being used (SMTPDirect or Sendmail) failed + # to deliver the message to one or all of the recipients. Push + # the delivery module back on the pipeline list and break. + pipeline.insert(0, modname) + # Consult and adjust some meager metrics that try to decide + # whether it's worth continuing to attempt delivery of this + # message. + now = time.time() + recips = msgdata['recips'] + last_recip_count = msgdata.get('last_recip_count', 0) + deliver_until = msgdata.get('deliver_until', now) + if len(recips) == last_recip_count: + # We didn't make any progress. How many times to we continue + # to attempt delivery? TBD: make this configurable. + if now > deliver_until: + # throw this message away + return 0 + else: + # Keep trying to delivery this for 3 days + deliver_until = now + 60*60*24*3 + msgdata['last_recip_count'] = len(recips) + msgdata['deliver_until'] = deliver_until break + except Exception, e: + # Some other exception occurred, which we definitely did not + # expect, so set this message up for queuing. This is mildly + # offensive since we're doing the equivalent of a bare except, + # which gobbles useful bug reporting. Still, it's more important + # that email not get lost, so we log the exception and the + # traceback so that we have a hope of fixing this. We may want to + # email the site admin or (shudder) the Mailman maintainers. + # + # We stick the name of the failed module back into the front of + # the pipeline list so that it can resume where it left off when + # qrunner tries to redeliver it. + pipeline.insert(0, modname) + mlist.LogMsg('error', 'Delivery exception: %s' % e) + s = StringIO() + traceback.print_exc(file=s) + mlist.LogMsg('error', s.getvalue()) + break + msgdata['pipeline'] = pipeline + return len(pipeline) -# for messages that arrive from the outside, to be delivered to all mailing -# list subscribers -def DeliverToList(mlist, msg): - pipeline = ['SpamDetect', - 'Approve', - 'Replybot', - 'Hold', - 'Cleanse', - 'CookHeaders', - 'ToDigest', - 'ToArchive', - 'ToUsenet', - 'CalcRecips', - 'Decorate', - mm_cfg.DELIVERY_MODULE, - 'Acknowledge', - 'AfterDelivery', - ] - pipeline_delivery(mlist, msg, pipeline) - - - -# for messages that qrunner tries to re-deliver +# For messages that qrunner tries to re-deliver using the pre 2.0beta3 qfiles +# data format. def RedeliverMessage(mlist, msg): - pipeline = [mm_cfg.DELIVERY_MODULE, - ] - pipeline_delivery(mlist, msg, pipeline) + msgdata = {'pipeline': [mm_cfg.DELIVERY_MODULE]} + return DeliverToList(mlist, msg, msgdata) @@ -78,5 +143,8 @@ def DeliverToUser(mlist, msg): 'CookHeaders', mm_cfg.DELIVERY_MODULE, ] - msg.fasttrack = 1 - pipeline_delivery(mlist, msg, pipeline) + msgdata = {'pipeline' : pipeline, + 'fasttrack': 1, + 'recips' : msg.recips, + } + return DeliverToList(mlist, msg, msgdata) |
