Postfix release 19991231 Patchlevel 12 is available. While processing massive amounts of one-recipient mail, the Postfix queue manager could deadlock for 10 seconds while sending a bounce message. In order to remedy this, all queue manager bounce send requests are now executed asynchronously. This problem was reported by El Bunzo (webpower.nl) and by Tiger Technologies (tigertech.com). To apply the fix, cd into the top-level release 19991231 patchlevel 11 source code directory and feed this text as standard input to Larry Wall's patch command: patch -p0 + /* + /* void abounce_flush(flags, queue, id, sender, callback, context) + /* int flags; + /* const char *queue; + /* const char *id; + /* const char *sender; + /* void (*callback)(int status, char *context); + /* char *context; + /* + /* void adefer_flush(flags, queue, id, sender, callback, context) + /* int flags; + /* const char *queue; + /* const char *id; + /* const char *sender; + /* void (*callback)(int status, char *context); + /* char *context; + /* + /* void adefer_warn(flags, queue, id, sender, callback, context) + /* int flags; + /* const char *queue; + /* const char *id; + /* const char *sender; + /* void (*callback)(int status, char *context); + /* char *context; + /* DESCRIPTION + /* This module implements an asynchronous interface to the + /* bounce/defer service for submitting sender notifications + /* without waiting for completion of the request. + /* + /* abounce_flush() bounces the specified message to + /* the specified sender, including the bounce log that was + /* built with bounce_append(). + /* + /* adefer_flush() bounces the specified message to + /* the specified sender, including the defer log that was + /* built with defer_append(). + /* + /* adefer_warn() sends a "mail is delayed" notification to + /* the specified sender, including the defer log that was + /* built with defer_append(). + /* + /* Arguments: + /* .IP flags + /* The bitwise OR of zero or more of the following (specify + /* BOUNCE_FLAG_NONE to request no special processing): + /* .RS + /* .IP BOUNCE_FLAG_CLEAN + /* Delete the bounce log in case of an error (as in: pretend + /* that we never even tried to bounce this message). + /* .IP BOUNCE_FLAG_COPY + /* Request that a postmaster copy is sent. + /* .RE + /* .IP queue + /* The message queue name of the original message file. + /* .IP id + /* The message queue id if the original message file. The bounce log + /* file has the same name as the original message file. + /* .IP sender + /* The sender envelope address. + /* .IP callback + /* Name of a routine that receives the notification status as + /* documented for bounce_flush() or defer_flush(). + /* .IP context + /* Application-specific context that is passed through to the + /* callback routine. Use proper casts or the world will come + /* to an end. + /* DIAGNOSTICS + /* In case of success, these functions log the action, and return a + /* zero result via the callback routine. Otherwise, the functions + /* return a non-zero result via the callback routine, and when + /* BOUNCE_FLAG_CLEAN is disabled, log that message delivery is deferred. + /* LICENSE + /* .ad + /* .fi + /* The Secure Mailer license must be distributed with this software. + /* AUTHOR(S) + /* Wietse Venema + /* IBM T.J. Watson Research + /* P.O. Box 704 + /* Yorktown Heights, NY 10598, USA + /*--*/ + + /* System library. */ + + #include + + /* Utility library. */ + + #include + #include + #include + #include + + /* Global library. */ + + #include + #include + + /* Application-specific. */ + + /* + * Each bounce/defer flush/warn request is implemented by sending the + * request to the bounce/defer server, and by creating a pseudo thread that + * suspends itself until the server replies (or dies). Upon wakeup, the + * pseudo thread delivers the request completion status to the application + * and destroys itself. The structure below maintains all the necessary + * request state while the pseudo thread is suspended. + */ + typedef struct { + int command; /* bounce request type */ + int flags; /* bounce options */ + char *id; /* queue ID for logging */ + ABOUNCE_FN callback; /* application callback */ + char *context; /* application context */ + VSTREAM *fp; /* server I/O handle */ + } ABOUNCE; + + /* abounce_done - deliver status to application and clean up pseudo thread */ + + static void abounce_done(ABOUNCE *ap, int status) + { + (void) vstream_fclose(ap->fp); + if (status != 0 && (ap->flags & BOUNCE_FLAG_CLEAN) == 0) + msg_info("%s: status=deferred (%s failed)", ap->id, + ap->command == BOUNCE_CMD_FLUSH ? "bounce" : + ap->command == BOUNCE_CMD_WARN ? "delay warning" : + "whatever"); + ap->callback(status, ap->context); + myfree(ap->id); + myfree((char *) ap); + } + + /* abounce_event - resume pseudo thread after server reply event */ + + static void abounce_event(int unused_event, char *context) + { + ABOUNCE *ap = (ABOUNCE *) context; + int status; + + event_disable_readwrite(vstream_fileno(ap->fp)); + abounce_done(ap, mail_scan(ap->fp, "%d", &status) == 1 ? status : -1); + } + + /* abounce_request - suspend pseudo thread until server reply event */ + + static void abounce_request(const char *class, const char *service, + int command, int flags, + const char *queue, const char *id, + const char *sender, + ABOUNCE_FN callback, + char *context) + { + ABOUNCE *ap; + + /* + * Save pseudo thread state. Connect to the server. Send the request and + * suspend the pseudo thread until the server replies (or dies). + */ + ap = (ABOUNCE *) mymalloc(sizeof(*ap)); + ap->command = command; + ap->flags = flags; + ap->id = mystrdup(id); + ap->callback = callback; + ap->context = context; + ap->fp = mail_connect_wait(class, service); + + if (mail_print(ap->fp, "%d %d %s %s %s %s", command, + flags, queue, id, sender, MAIL_EOF) == 0 + && vstream_fflush(ap->fp) == 0) { + event_enable_read(vstream_fileno(ap->fp), abounce_event, (char *) ap); + } else { + abounce_done(ap, -1); + } + } + + /* abounce_flush - asynchronous bounce flush */ + + void abounce_flush(int flags, const char *queue, const char *id, + const char *sender, ABOUNCE_FN callback, char *context) + { + abounce_request(MAIL_CLASS_PRIVATE, MAIL_SERVICE_BOUNCE, BOUNCE_CMD_FLUSH, + flags, queue, id, sender, callback, context); + } + + /* adefer_flush - asynchronous defer flush */ + + void adefer_flush(int flags, const char *queue, const char *id, + const char *sender, ABOUNCE_FN callback, char *context) + { + abounce_request(MAIL_CLASS_PRIVATE, MAIL_SERVICE_DEFER, BOUNCE_CMD_FLUSH, + flags, queue, id, sender, callback, context); + } + + /* adefer_warn - send copy of defer log to sender as warning bounce */ + + void adefer_warn(int flags, const char *queue, const char *id, + const char *sender, ABOUNCE_FN callback, char *context) + { + abounce_request(MAIL_CLASS_PRIVATE, MAIL_SERVICE_DEFER, BOUNCE_CMD_WARN, + flags, queue, id, sender, callback, context); + } diff -cr --new-file --exclude=.indent.pro ../postfix-19991231-pl11/global/abounce.h ./global/abounce.h *** ../postfix-19991231-pl11/global/abounce.h Wed Dec 31 19:00:00 1969 --- ./global/abounce.h Thu Dec 7 15:55:42 2000 *************** *** 0 **** --- 1,39 ---- + #ifndef _ABOUNCE_H_INCLUDED_ + #define _ABOUNCE_H_INCLUDED_ + + /*++ + /* NAME + /* abounce 3h + /* SUMMARY + /* asynchronous bounce/defer service client + /* SYNOPSIS + /* #include + /* DESCRIPTION + /* .nf + + /* + * Global library. + */ + #include + + /* + * Client interface. + */ + typedef void (*ABOUNCE_FN) (int, char *); + + extern void abounce_flush(int, const char *, const char *, const char *, ABOUNCE_FN, char *); + extern void adefer_flush(int, const char *, const char *, const char *, ABOUNCE_FN, char *); + extern void adefer_warn(int, const char *, const char *, const char *, ABOUNCE_FN, char *); + + /* LICENSE + /* .ad + /* .fi + /* The Secure Mailer license must be distributed with this software. + /* AUTHOR(S) + /* Wietse Venema + /* IBM T.J. Watson Research + /* P.O. Box 704 + /* Yorktown Heights, NY 10598, USA + /*--*/ + + #endif diff -cr --new-file --exclude=.indent.pro ../postfix-19991231-pl11/qmgr/Makefile.in ./qmgr/Makefile.in *** ../postfix-19991231-pl11/qmgr/Makefile.in Tue Nov 21 20:01:50 2000 --- ./qmgr/Makefile.in Fri Dec 8 13:22:53 2000 *************** *** 92,97 **** --- 92,98 ---- qmgr_active.o: ../include/recipient_list.h qmgr_active.o: ../include/bounce.h qmgr_active.o: ../include/defer.h + qmgr_active.o: ../include/abounce.h qmgr_active.o: ../include/rec_type.h qmgr_active.o: qmgr.h qmgr_active.o: ../include/scan_dir.h diff -cr --new-file --exclude=.indent.pro ../postfix-19991231-pl11/qmgr/qmgr_active.c ./qmgr/qmgr_active.c *** ../postfix-19991231-pl11/qmgr/qmgr_active.c Wed Dec 8 22:10:41 1999 --- ./qmgr/qmgr_active.c Fri Dec 8 10:58:42 2000 *************** *** 102,113 **** --- 102,123 ---- #include #include #include + #include #include /* Application-specific. */ #include "qmgr.h" + /* + * A bunch of call-back routines. + */ + static void qmgr_active_done_2_bounce_flush(int, char *); + static void qmgr_active_done_2_generic(QMGR_MESSAGE *); + static void qmgr_active_done_3_defer_flush(int, char *); + static void qmgr_active_done_3_defer_warn(int, char *); + static void qmgr_active_done_3_generic(QMGR_MESSAGE *); + /* qmgr_active_corrupt - move corrupted file out of the way */ static void qmgr_active_corrupt(const char *queue_id) *************** *** 235,242 **** { char *myname = "qmgr_active_done"; struct stat st; - const char *path; - int delay; if (msg_verbose) msg_info("%s: %s", myname, message->queue_id); --- 245,250 ---- *************** *** 255,260 **** --- 263,271 ---- * Don't bounce when the bounce log is empty. The bounce process obviously * failed, and the delivery agent will have requested that the message be * deferred. + * + * Bounces are sent asynchronously to avoid stalling while the cleanup + * daemon waits for the qmgr to accept the "new mail" trigger. */ if (stat(mail_queue_path((VSTRING *) 0, MAIL_QUEUE_BOUNCE, message->queue_id), &st) == 0) { if (st.st_size == 0) { *************** *** 264,277 **** } else { if (msg_verbose) msg_info("%s: bounce %s", myname, message->queue_id); ! message->flags |= bounce_flush(BOUNCE_FLAG_KEEP, ! message->queue_name, ! message->queue_id, ! message->errors_to); } } /* * A delivery agent marks a queue file as corrupt by changing its * attributes, and by pretending that delivery was deferred. */ --- 275,318 ---- } else { if (msg_verbose) msg_info("%s: bounce %s", myname, message->queue_id); ! abounce_flush(BOUNCE_FLAG_KEEP, ! message->queue_name, ! message->queue_id, ! message->errors_to, ! qmgr_active_done_2_bounce_flush, ! (char *) message); ! return; } } /* + * Asynchronous processing does not reach this point. + */ + qmgr_active_done_2_generic(message); + } + + /* qmgr_active_done_2_bounce_flush - process abounce_flush() status */ + + static void qmgr_active_done_2_bounce_flush(int status, char *context) + { + QMGR_MESSAGE *message = (QMGR_MESSAGE *) context; + + /* + * Process abounce_flush() status and continue processing. + */ + message->flags |= status; + qmgr_active_done_2_generic(message); + } + + /* qmgr_active_done_2_generic - continue processing */ + + static void qmgr_active_done_2_generic(QMGR_MESSAGE *message) + { + char *myname = "qmgr_active_done_2_generic"; + const char *path; + struct stat st; + + /* * A delivery agent marks a queue file as corrupt by changing its * attributes, and by pretending that delivery was deferred. */ *************** *** 304,309 **** --- 345,353 ---- /* * If we get to this point we have tried all recipients for this message. * If the message is too old, try to bounce it. + * + * Bounces are sent asynchronously to avoid stalling while the cleanup + * daemon waits for the qmgr to accept the "new mail" trigger. */ #define HOUR 3600 #define DAY 86400 *************** *** 312,333 **** if (event_time() > message->arrival_time + var_max_queue_time * DAY) { if (msg_verbose) msg_info("%s: too old, bouncing %s", myname, message->queue_id); ! message->flags = defer_flush(BOUNCE_FLAG_KEEP, ! message->queue_name, ! message->queue_id, ! message->errors_to); } else if (message->warn_time > 0 && event_time() > message->warn_time) { if (msg_verbose) msg_info("%s: sending defer warning for %s", myname, message->queue_id); ! if (defer_warn(BOUNCE_FLAG_KEEP, ! message->queue_name, ! message->queue_id, ! message->errors_to) == 0) { ! qmgr_message_update_warn(message); ! } } } /* * Some recipients need to be tried again. Move the queue file time --- 356,421 ---- if (event_time() > message->arrival_time + var_max_queue_time * DAY) { if (msg_verbose) msg_info("%s: too old, bouncing %s", myname, message->queue_id); ! adefer_flush(BOUNCE_FLAG_KEEP, ! message->queue_name, ! message->queue_id, ! message->errors_to, ! qmgr_active_done_3_defer_flush, ! (char *) message); ! return; } else if (message->warn_time > 0 && event_time() > message->warn_time) { if (msg_verbose) msg_info("%s: sending defer warning for %s", myname, message->queue_id); ! adefer_warn(BOUNCE_FLAG_KEEP, ! message->queue_name, ! message->queue_id, ! message->errors_to, ! qmgr_active_done_3_defer_warn, ! (char *) message); ! return; } } + + /* + * Asynchronous processing does not reach this point. + */ + qmgr_active_done_3_generic(message); + } + + /* qmgr_active_done_3_defer_warn - continue after adefer_warn() completion */ + + static void qmgr_active_done_3_defer_warn(int status, char *context) + { + QMGR_MESSAGE *message = (QMGR_MESSAGE *) context; + + /* + * Process adefer_warn() completion status and continue processing. + */ + if (status == 0) + qmgr_message_update_warn(message); + qmgr_active_done_3_generic(message); + } + + /* qmgr_active_done_3_defer_flush - continue after adefer_flush() completion */ + + static void qmgr_active_done_3_defer_flush(int status, char *context) + { + QMGR_MESSAGE *message = (QMGR_MESSAGE *) context; + + /* + * Process adefer_flush() status and continue processing. + */ + message->flags = status; + qmgr_active_done_3_generic(message); + } + + /* qmgr_active_done_3_generic - continue processing */ + + static void qmgr_active_done_3_generic(QMGR_MESSAGE *message) + { + char *myname = "qmgr_active_done_3_generic"; + int delay; /* * Some recipients need to be tried again. Move the queue file time