@@ -81,22 +81,22 @@ def __init__(self, slice=None, numslices=1):
8181 self ._last_cleanup = time .time ()
8282
8383 # We look this function up only at startup time
84- self . _modname = 'Mailman.Handlers.' + mm_cfg .DELIVERY_MODULE
85- mailman_log ('debug' , 'OutgoingRunner: Attempting to import delivery module: %s' , self . _modname )
84+ modname = 'Mailman.Handlers.' + mm_cfg .DELIVERY_MODULE
85+ mailman_log ('debug' , 'OutgoingRunner: Attempting to import delivery module: %s' , modname )
8686
8787 try :
88- mod = __import__ (self . _modname )
88+ mod = __import__ (modname )
8989 mailman_log ('debug' , 'OutgoingRunner: Successfully imported delivery module' )
9090 except ImportError as e :
91- mailman_log ('error' , 'OutgoingRunner: Failed to import delivery module %s: %s' , self . _modname , str (e ))
91+ mailman_log ('error' , 'OutgoingRunner: Failed to import delivery module %s: %s' , modname , str (e ))
9292 mailman_log ('error' , 'OutgoingRunner: Traceback: %s' , traceback .format_exc ())
9393 raise
9494
9595 try :
96- self ._func = getattr (sys .modules [self . _modname ], 'process' )
96+ self ._func = getattr (sys .modules [modname ], 'process' )
9797 mailman_log ('debug' , 'OutgoingRunner: Successfully got process function from module' )
9898 except AttributeError as e :
99- mailman_log ('error' , 'OutgoingRunner: Failed to get process function from module %s: %s' , self . _modname , str (e ))
99+ mailman_log ('error' , 'OutgoingRunner: Failed to get process function from module %s: %s' , modname , str (e ))
100100 mailman_log ('error' , 'OutgoingRunner: Traceback: %s' , traceback .format_exc ())
101101 raise
102102
@@ -242,100 +242,77 @@ def _validate_message(self, msg, msgdata):
242242 return msg , False
243243
244244 def _dispose (self , mlist , msg , msgdata ):
245- """Process an outgoing message."""
246- msgid = msg .get ('message-id' , 'n/a' )
247- filebase = msgdata .get ('_filebase' , 'unknown' )
248-
249- # Log the full msgdata at the start of processing
250- mailman_log ('debug' , 'OutgoingRunner._dispose: Full msgdata at start:\n %s' , str (msgdata ))
251-
252- # Ensure we have a MailList object
253- if isinstance (mlist , str ):
254- try :
255- mlist = get_mail_list ()(mlist , lock = 0 )
256- should_unlock = True
257- except Errors .MMUnknownListError :
258- mailman_log ('error' , 'OutgoingRunner: Unknown list %s' , mlist )
259- self ._shunt .enqueue (msg , msgdata )
260- return True
261- else :
262- should_unlock = False
263-
245+ # See if we should retry delivery of this message again.
246+ deliver_after = msgdata .get ('deliver_after' , 0 )
247+ if time .time () < deliver_after :
248+ return True
249+ # Make sure we have the most up-to-date state
250+ mlist .Load ()
264251 try :
265- mailman_log ('debug' , 'OutgoingRunner._dispose: Starting to process outgoing message %s (file: %s) for list %s' ,
266- msgid , filebase , mlist .internal_name ())
267-
268- # Check retry delay and duplicate processing
269- if not self ._check_retry_delay (msgid , filebase ):
270- mailman_log ('debug' , 'OutgoingRunner._dispose: Message %s failed retry delay check, skipping' , msgid )
271- return False
272-
273- # Make sure we have the most up-to-date state
274- try :
275- mlist .Load ()
276- mailman_log ('debug' , 'OutgoingRunner._dispose: Successfully loaded list %s' , mlist .internal_name ())
277- except Errors .MMCorruptListDatabaseError as e :
278- mailman_log ('error' , 'OutgoingRunner._dispose: Failed to load list %s: %s\n Traceback:\n %s' ,
279- mlist .internal_name (), str (e ), traceback .format_exc ())
280- self ._unmark_message_processed (msgid )
281- return False
282- except Exception as e :
283- mailman_log ('error' , 'OutgoingRunner._dispose: Unexpected error loading list %s: %s\n Traceback:\n %s' ,
284- mlist .internal_name (), str (e ), traceback .format_exc ())
285- self ._unmark_message_processed (msgid )
286- return False
287-
288- # Validate message type first
289- msg , success = self ._validate_message (msg , msgdata )
290- if not success :
291- mailman_log ('error' , 'OutgoingRunner._dispose: Message validation failed for message %s' , msgid )
292- self ._unmark_message_processed (msgid )
293- return False
294-
295- # Log the full msgdata after validation
296- mailman_log ('debug' , 'OutgoingRunner._dispose: Full msgdata after validation:\n %s' , str (msgdata ))
297-
298- # Validate message headers
299- if not msg .get ('message-id' ):
300- mailman_log ('error' , 'OutgoingRunner._dispose: Message missing Message-ID header' )
301- self ._unmark_message_processed (msgid )
302- return False
303-
304- # Process the outgoing message
305- try :
306- mailman_log ('debug' , 'OutgoingRunner._dispose: Processing outgoing message %s' , msgid )
307-
308- # Get message type and recipient
309- msgtype = msgdata .get ('_msgtype' , 'unknown' )
310- recipient = msgdata .get ('recipient' , 'unknown' )
311-
312- mailman_log ('debug' , 'OutgoingRunner._dispose: Message %s is type %s for recipient %s' ,
313- msgid , msgtype , recipient )
314-
315- # Process based on message type
316- if msgtype == 'bounce' :
317- success = self ._process_bounce (mlist , msg , msgdata )
318- elif msgtype == 'admin' :
319- success = self ._process_admin (mlist , msg , msgdata )
320- else :
321- success = self ._process_regular (mlist , msg , msgdata )
322-
323- if success :
324- mailman_log ('debug' , 'OutgoingRunner._dispose: Successfully processed outgoing message %s' , msgid )
325- return True
326- else :
327- mailman_log ('error' , 'OutgoingRunner._dispose: Failed to process outgoing message %s' , msgid )
328- return False
329-
330- except Exception as e :
331- mailman_log ('error' , 'OutgoingRunner._dispose: Error processing outgoing message %s: %s\n Traceback:\n %s' ,
332- msgid , str (e ), traceback .format_exc ())
333- self ._unmark_message_processed (msgid )
334- return False
335-
336- finally :
337- if should_unlock :
338- mlist .Unlock ()
252+ pid = os .getpid ()
253+ self ._func (mlist , msg , msgdata )
254+ # Failsafe -- a child may have leaked through.
255+ if pid != os .getpid ():
256+ mailman_log ('error' , 'child process leaked thru: %s' , mm_cfg .DELIVERY_MODULE )
257+ os ._exit (1 )
258+ self .__logged = False
259+ except socket .error :
260+ # There was a problem connecting to the SMTP server. Log this
261+ # once, but crank up our sleep time so we don't fill the error
262+ # log.
263+ port = mm_cfg .SMTPPORT
264+ if port == 0 :
265+ port = 'smtp'
266+ # Log this just once.
267+ if not self .__logged :
268+ mailman_log ('error' , 'Cannot connect to SMTP server %s on port %s' ,
269+ mm_cfg .SMTPHOST , port )
270+ self .__logged = True
271+ self ._snooze (0 )
272+ return True
273+ except Errors .SomeRecipientsFailed as e :
274+ # Handle local rejects of probe messages differently.
275+ if msgdata .get ('probe_token' ) and e .permfailures :
276+ self ._probe_bounce (mlist , msgdata ['probe_token' ])
277+ else :
278+ # Delivery failed at SMTP time for some or all of the
279+ # recipients. Permanent failures are registered as bounces,
280+ # but temporary failures are retried for later.
281+ #
282+ # BAW: msg is going to be the original message that failed
283+ # delivery, not a bounce message. This may be confusing if
284+ # this is what's sent to the user in the probe message. Maybe
285+ # we should craft a bounce-like message containing information
286+ # about the permanent SMTP failure?
287+ if e .permfailures :
288+ self ._queue_bounces (mlist .internal_name (), e .permfailures ,
289+ msg )
290+ # Move temporary failures to the qfiles/retry queue which will
291+ # occasionally move them back here for another shot at
292+ # delivery.
293+ if e .tempfailures :
294+ now = time .time ()
295+ recips = e .tempfailures
296+ last_recip_count = msgdata .get ('last_recip_count' , 0 )
297+ deliver_until = msgdata .get ('deliver_until' , now )
298+ if len (recips ) == last_recip_count :
299+ # We didn't make any progress, so don't attempt
300+ # delivery any longer. BAW: is this the best
301+ # disposition?
302+ if now > deliver_until :
303+ return False
304+ else :
305+ # Keep trying to delivery this message for a while
306+ deliver_until = now + mm_cfg .DELIVERY_RETRY_PERIOD
307+ # Don't retry delivery too soon.
308+ deliver_after = now + mm_cfg .DELIVERY_RETRY_WAIT
309+ msgdata ['deliver_after' ] = deliver_after
310+ msgdata ['last_recip_count' ] = len (recips )
311+ msgdata ['deliver_until' ] = deliver_until
312+ msgdata ['recips' ] = recips
313+ self .__retryq .enqueue (msg , msgdata )
314+ # We've successfully completed handling of this message
315+ return False
339316
340317 def _process_bounce (self , mlist , msg , msgdata ):
341318 """Process a bounce message."""
0 commit comments