From 93d0a26a92f7bb09a35f4463f2ab7cad7c62b6cb Mon Sep 17 00:00:00 2001 From: Wietse Venema Date: Fri, 30 Nov 2007 00:00:00 -0500 Subject: [PATCH] postfix-2.5-20071202 --- postfix/HISTORY | 17 ++++- postfix/README_FILES/SCHEDULER_README | 24 +++---- postfix/RELEASE_NOTES | 8 +++ postfix/html/SCHEDULER_README.html | 8 +-- postfix/html/oqmgr.8.html | 46 ++++++++----- postfix/html/postconf.5.html | 95 +++++++++++++++++++++++++++ postfix/html/qmgr.8.html | 48 +++++++++----- postfix/man/man5/postconf.5 | 70 ++++++++++++++++++++ postfix/man/man8/oqmgr.8 | 12 +++- postfix/man/man8/qmgr.8 | 12 +++- postfix/mantools/postlink | 4 +- postfix/proto/SCHEDULER_README.html | 8 +-- postfix/proto/postconf.proto | 87 ++++++++++++++++++++++++ postfix/src/global/mail_params.h | 5 ++ postfix/src/oqmgr/qmgr.c | 14 +++- postfix/src/oqmgr/qmgr.h | 31 ++++++++- postfix/src/oqmgr/qmgr_deliver.c | 4 +- postfix/src/oqmgr/qmgr_enable.c | 4 +- postfix/src/oqmgr/qmgr_entry.c | 28 ++++++-- postfix/src/oqmgr/qmgr_queue.c | 78 +++++++++++++++++++--- postfix/src/oqmgr/qmgr_transport.c | 5 ++ postfix/src/postconf/auto.awk | 1 + postfix/src/qmgr/qmgr.c | 14 +++- postfix/src/qmgr/qmgr.h | 31 ++++++++- postfix/src/qmgr/qmgr_deliver.c | 4 +- postfix/src/qmgr/qmgr_enable.c | 4 +- postfix/src/qmgr/qmgr_entry.c | 43 ++++++++---- postfix/src/qmgr/qmgr_queue.c | 76 +++++++++++++++++++-- postfix/src/qmgr/qmgr_transport.c | 5 ++ 29 files changed, 675 insertions(+), 111 deletions(-) diff --git a/postfix/HISTORY b/postfix/HISTORY index 6bbc5c766..50aee39bd 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -13863,5 +13863,18 @@ Apologies for any names omitted. _destination_concurrency_positive_feedback, _destination_concurrency_failed_cohort_limit. - Files: global/mail_params.h, qmgr/qmgr.c, qmgr/qmgr_transport.c, - qmgr/qmge_queue.c, qmgr/qmgr_feedback.c, postconf/auto.awk. + Files: global/mail_params.h, *qmgr/qmgr.c, *qmgr/qmgr_transport.c, + *qmgr/qmgr_queue.c, *qmgr/qmgr_feedback.c, postconf/auto.awk. + +20071202 + + Feature: output rate control. For example, specify + "smtp_delivery_rate_delay = 5m" to insert a five-minute + delay between deliveries. This was an opportunity to define + the mutually exclusive states that a queue can have, and + to detect invalid transitions. This will make adding new + features code easier. Files: *qmgr/qmgr_transport.c, + *qmgr/qmgr_queue.c, *qmgr/qmgr_entry.c. + + Bugfix: don't update the back-to-back delivery time stamp + while deferring mail. File: *qmgr/qmgr_entry.c. diff --git a/postfix/README_FILES/SCHEDULER_README b/postfix/README_FILES/SCHEDULER_README index a335765bc..cba526863 100644 --- a/postfix/README_FILES/SCHEDULER_README +++ b/postfix/README_FILES/SCHEDULER_README @@ -351,14 +351,14 @@ next section. LLiimmiittaattiioonnss ooff lleessss--tthhaann--11 ppeerr ddeelliivveerryy ffeeeeddbbaacckk -The delivery concurrency scheduler with less-than-1 concurrency feedback per -delivery solves a problem with servers that have active concurrency limiters. -This works only because feedback is handled in a peculiar manner: positive -feedback will increment the concurrency by 1 at the eenndd of a sequence of events -of length 1/feedback, while negative feedback will decrement concurrency by 1 -at the bbeeggiinnnniinngg of such a sequence. This is how Postfix adjusts quickly for -overshoot without causing lots of mail to be deferred. Without this difference -in feedback treatment, less-than-1 feedback per delivery would defer 50% of the +The scheduler with less-than-1 concurrency feedback per delivery solves a +problem with servers that have active concurrency limiters. This works only +because feedback is handled in a peculiar manner: positive feedback will +increment the concurrency by 1 at the eenndd of a sequence of events of length 1/ +feedback, while negative feedback will decrement concurrency by 1 at the +bbeeggiinnnniinngg of such a sequence. This is how Postfix adjusts quickly for overshoot +without causing lots of mail to be deferred. Without this difference in +feedback treatment, less-than-1 feedback per delivery would defer 50% of the mail, and would be no better in this respect than the old +/-1 feedback per delivery. @@ -374,10 +374,10 @@ once it reaches a concurrency level of about K, even though the good servers behind the load balancer are perfectly capable of handling more traffic. This noise problem gets worse as the amount of positive feedback per delivery -gets smaller. A compromise is to avoid concurrency-dependent positive feedback, -and to use fixed less-than-1 feedback values instead. For example, to tolerate -1 of 4 bad servers in the above load balancer scenario, use positive feedback -of 1/4 per "good" delivery (no connect or handshake error), and use an equal or +gets smaller. A compromise is to use fixed less-than-1 positive feedback values +instead of concurrency-dependent positive feedback. For example, to tolerate 1 +of 4 bad servers in the above load balancer scenario, use positive feedback of +1/4 per "good" delivery (no connect or handshake error), and use an equal or smaller amount of negative feedback per "bad" delivery. The downside of using concurrency-independent feedback is that some of the old +/-1 feedback problems will return at large concurrencies. Sites that deliver at non-trivial per- diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index 80dd1e5f7..60752c5c4 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -17,6 +17,14 @@ Incompatibility with Postfix 2.3 and earlier If you upgrade from Postfix 2.3 or earlier, read RELEASE_NOTES-2.4 before proceeding. +Major changes with Postfix snapshot 20071202 +============================================ + +Output rate control in the queue manager. For example, specify +"smtp_delivery_rate_delay = 5m", to pause five minutes between +message deliveries. More information in the postconf(5) manual +under "default_delivery_rate_delay". + Major changes with Postfix snapshot 20071130 ============================================ diff --git a/postfix/html/SCHEDULER_README.html b/postfix/html/SCHEDULER_README.html index 015a6d0f0..cc8cea13e 100644 --- a/postfix/html/SCHEDULER_README.html +++ b/postfix/html/SCHEDULER_README.html @@ -554,7 +554,7 @@ the next section.

Limitations of less-than-1 per delivery feedback

-

The delivery concurrency scheduler with less-than-1 concurrency +

The scheduler with less-than-1 concurrency feedback per delivery solves a problem with servers that have active concurrency limiters. This works only because feedback is handled in a peculiar manner: positive feedback will increment the concurrency @@ -580,9 +580,9 @@ level of about K, even though the good servers behind the load balancer are perfectly capable of handling more traffic.

This noise problem gets worse as the amount of positive feedback -per delivery gets smaller. A compromise is to avoid concurrency-dependent -positive feedback, and to use fixed less-than-1 feedback values -instead. For example, to tolerate 1 of 4 bad servers in the above +per delivery gets smaller. A compromise is to use fixed less-than-1 +positive feedback values instead of concurrency-dependent positive +feedback. For example, to tolerate 1 of 4 bad servers in the above load balancer scenario, use positive feedback of 1/4 per "good" delivery (no connect or handshake error), and use an equal or smaller amount of negative feedback per "bad" delivery. The downside of diff --git a/postfix/html/oqmgr.8.html b/postfix/html/oqmgr.8.html index ff0d14842..8e6258143 100644 --- a/postfix/html/oqmgr.8.html +++ b/postfix/html/oqmgr.8.html @@ -242,18 +242,18 @@ OQMGR(8) OQMGR(8) Idem, for delivery via the named message transport. default_destination_concurrency_negative_feedback (1) - The per-destination amount of negative delivery - concurrency feedback, after a delivery completes - with a connection or handshake failure. + The per-destination amount of delivery concurrency + negative feedback, after a delivery completes with + a connection or handshake failure. transport_destination_concurrency_negative_feedback ($default_destination_concurrency_negative_feedback) Idem, for delivery via the named message transport. default_destination_concurrency_positive_feedback (1) - The per-destination amount of positive delivery - concurrency feedback, after a delivery completes - without connection or handshake failure. + The per-destination amount of delivery concurrency + positive feedback, after a delivery completes with- + out connection or handshake failure. transport_destination_concurrency_positive_feedback ($default_destination_concurrency_positive_feedback) @@ -301,14 +301,26 @@ OQMGR(8) OQMGR(8) The maximal time a bounce message is queued before it is considered undeliverable. + Available in Postfix version 2.5 and later: + + default_delivery_rate_delay (0s) + The default amount of delay that is inserted + between individual deliveries to the same destina- + tion; with per-destination recipient limit > 1, a + destination is a domain, otherwise it is a recipi- + ent. + + transport_delivery_rate_delay $default_delivery_rate_delay + Idem, for delivery via the named message transport. + MISCELLANEOUS CONTROLS config_directory (see 'postconf -d' output) - The default location of the Postfix main.cf and + The default location of the Postfix main.cf and master.cf configuration files. daemon_timeout (18000s) - How much time a Postfix daemon process may take to - handle a request before it is terminated by a + How much time a Postfix daemon process may take to + handle a request before it is terminated by a built-in watchdog timer. defer_transports (empty) @@ -317,11 +329,11 @@ OQMGR(8) OQMGR(8) "sendmail -q" or equivalent. delay_logging_resolution_limit (2) - The maximal number of digits after the decimal + The maximal number of digits after the decimal point when logging sub-second delay values. helpful_warnings (yes) - Log warnings about problematic configuration set- + Log warnings about problematic configuration set- tings, and provide helpful suggestions. ipc_timeout (3600s) @@ -329,23 +341,23 @@ OQMGR(8) OQMGR(8) over an internal communication channel. process_id (read-only) - The process ID of a Postfix command or daemon + The process ID of a Postfix command or daemon process. process_name (read-only) - The process name of a Postfix command or daemon + The process name of a Postfix command or daemon process. queue_directory (see 'postconf -d' output) - The location of the Postfix top-level queue direc- + The location of the Postfix top-level queue direc- tory. syslog_facility (mail) The syslog facility of Postfix logging. syslog_name (postfix) - The mail system name that is prepended to the - process name in syslog records, so that "smtpd" + The mail system name that is prepended to the + process name in syslog records, so that "smtpd" becomes, for example, "postfix/smtpd". FILES @@ -368,7 +380,7 @@ OQMGR(8) OQMGR(8) QSHAPE_README, Postfix queue analysis LICENSE - The Secure Mailer license must be distributed with this + The Secure Mailer license must be distributed with this software. AUTHOR(S) diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index 94ed21349..b81212e4c 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -1584,6 +1584,35 @@ Examples: + + +

default_delivery_rate_delay +(default: 0s)
+ +

The default amount of delay that is inserted between individual +deliveries to the same destination; with per-destination recipient +limit > 1, a destination is a domain, otherwise it is a recipient. +

+ +

To enable the delay, specify a non-zero time value (an integral +value plus an optional one-letter suffix that specifies the time +unit).

+ +

Time units: s (seconds), m (minutes), h (hours), d (days), w +(weeks). The default time unit is s (seconds).

+ +

NOTE: the delay is enforced by the queue manager. The delay +timer state does not survive "postfix reload" or "postfix stop". +

+ +

Use transport_delivery_rate_delay to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+ +

This feature is available in Postfix 2.5 and later.

+ +
default_delivery_slot_cost @@ -1623,6 +1652,11 @@ message response times while making sure the mailing-list deliveries are not extended by more than 20-25 percent even in the worst case.

+

Use transport_delivery_slot_cost to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+

Examples:

@@ -1653,6 +1687,11 @@ Note that the full amount will still have to be accumulated before another preemption can take place later.

+

Use transport_delivery_slot_discount to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+ @@ -1674,6 +1713,11 @@ Note that the full amount will still have to be accumulated before another preemption can take place later.

+

Use transport_delivery_slot_loan to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+ @@ -1707,6 +1751,13 @@ is compatible with earlier Postfix versions.

The default maximal number of parallel deliveries to the same destination. This is the default limit for delivery via the lmtp(8), pipe(8), smtp(8) and virtual(8) delivery agents. +With per-destination recipient limit > 1, a destination is a domain, +otherwise it is a recipient. +

+ +

Use transport_destination_concurrency_limit to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport.

@@ -1840,6 +1891,11 @@ This is the default limit for delivery via the lmtp(8) the corresponding per-destination concurrency limit from concurrency per domain into concurrency per recipient.

+

Use transport_destination_recipient_limit to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+ @@ -1855,6 +1911,11 @@ recipients slots for the chosen message in order to avoid performance degradation.

+

Use transport_extra_recipient_limit to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+ @@ -1868,6 +1929,11 @@ which would never accumulate at least this many delivery slots (subject to slot cost parameter as well) are never preempted.

+

Use transport_minimum_delivery_slots to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+ @@ -2034,6 +2100,11 @@ to the respective transports. See also qmgr_message_recipient_minimum.

+

Use transport_recipient_limit to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+ @@ -2048,6 +2119,11 @@ make sure the recipients are refilled in timely manner even when $default_recipient_refill_limit is too high for too slow deliveries.

+

Use transport_recipient_refill_delay to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+

This feature is available in Postfix 2.4 and later.

@@ -2064,6 +2140,11 @@ $default_recipient_refi lower than this when this limit is too high for too slow deliveries.

+

Use transport_recipient_refill_limit to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+

This feature is available in Postfix 2.4 and later.

@@ -3187,6 +3268,8 @@ Examples: The initial per-destination concurrency level for parallel delivery to the same destination. This limit applies to delivery via smtp(8), and via the pipe(8) and virtual(8) delivery agents. +With per-destination recipient limit > 1, a destination is a domain, +otherwise it is a recipient.

Use transport_initial_destination_concurrency to specify @@ -12204,6 +12287,18 @@ This feature is available in Postfix 2.1 and later.

+ + +
transport_delivery_rate_delay +(default: $default_delivery_rate_delay)
+ +

A transport-specific override for the default_recipient_refill_delay +parameter value, where transport is the master.cf name of +the message delivery transport.

+ +

This feature is available in Postfix 2.5 and later.

+ +
transport_delivery_slot_cost diff --git a/postfix/html/qmgr.8.html b/postfix/html/qmgr.8.html index 331cbbb3b..ab4b8fabd 100644 --- a/postfix/html/qmgr.8.html +++ b/postfix/html/qmgr.8.html @@ -280,18 +280,18 @@ QMGR(8) QMGR(8) Idem, for delivery via the named message transport. default_destination_concurrency_negative_feedback (1) - The per-destination amount of negative delivery - concurrency feedback, after a delivery completes - with a connection or handshake failure. + The per-destination amount of delivery concurrency + negative feedback, after a delivery completes with + a connection or handshake failure. transport_destination_concurrency_negative_feedback ($default_destination_concurrency_negative_feedback) Idem, for delivery via the named message transport. default_destination_concurrency_positive_feedback (1) - The per-destination amount of positive delivery - concurrency feedback, after a delivery completes - without connection or handshake failure. + The per-destination amount of delivery concurrency + positive feedback, after a delivery completes with- + out connection or handshake failure. transport_destination_concurrency_positive_feedback ($default_destination_concurrency_positive_feedback) @@ -332,7 +332,7 @@ QMGR(8) QMGR(8) The default value for transport-specific _deliv- ery_slot_discount settings. - transport_delivery_slot_discount ($default_deliv- + transport_delivery_slot_discount ($default_deliv- ery_slot_discount) Idem, for delivery via the named message transport. @@ -373,14 +373,26 @@ QMGR(8) QMGR(8) The maximal time a bounce message is queued before it is considered undeliverable. + Available in Postfix version 2.5 and later: + + default_delivery_rate_delay (0s) + The default amount of delay that is inserted + between individual deliveries to the same destina- + tion; with per-destination recipient limit > 1, a + destination is a domain, otherwise it is a recipi- + ent. + + transport_delivery_rate_delay $default_delivery_rate_delay + Idem, for delivery via the named message transport. + MISCELLANEOUS CONTROLS config_directory (see 'postconf -d' output) - The default location of the Postfix main.cf and + The default location of the Postfix main.cf and master.cf configuration files. daemon_timeout (18000s) - How much time a Postfix daemon process may take to - handle a request before it is terminated by a + How much time a Postfix daemon process may take to + handle a request before it is terminated by a built-in watchdog timer. defer_transports (empty) @@ -389,11 +401,11 @@ QMGR(8) QMGR(8) "sendmail -q" or equivalent. delay_logging_resolution_limit (2) - The maximal number of digits after the decimal + The maximal number of digits after the decimal point when logging sub-second delay values. helpful_warnings (yes) - Log warnings about problematic configuration set- + Log warnings about problematic configuration set- tings, and provide helpful suggestions. ipc_timeout (3600s) @@ -401,23 +413,23 @@ QMGR(8) QMGR(8) over an internal communication channel. process_id (read-only) - The process ID of a Postfix command or daemon + The process ID of a Postfix command or daemon process. process_name (read-only) - The process name of a Postfix command or daemon + The process name of a Postfix command or daemon process. queue_directory (see 'postconf -d' output) - The location of the Postfix top-level queue direc- + The location of the Postfix top-level queue direc- tory. syslog_facility (mail) The syslog facility of Postfix logging. syslog_name (postfix) - The mail system name that is prepended to the - process name in syslog records, so that "smtpd" + The mail system name that is prepended to the + process name in syslog records, so that "smtpd" becomes, for example, "postfix/smtpd". FILES @@ -441,7 +453,7 @@ QMGR(8) QMGR(8) QSHAPE_README, Postfix queue analysis LICENSE - The Secure Mailer license must be distributed with this + The Secure Mailer license must be distributed with this software. AUTHOR(S) diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index d08793749..0620f2267 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -877,6 +877,26 @@ default_database_type = dbm .fi .ad .ft R +.SH default_delivery_rate_delay (default: 0s) +The default amount of delay that is inserted between individual +deliveries to the same destination; with per-destination recipient +limit > 1, a destination is a domain, otherwise it is a recipient. +.PP +To enable the delay, specify a non-zero time value (an integral +value plus an optional one-letter suffix that specifies the time +unit). +.PP +Time units: s (seconds), m (minutes), h (hours), d (days), w +(weeks). The default time unit is s (seconds). +.PP +NOTE: the delay is enforced by the queue manager. The delay +timer state does not survive "postfix reload" or "postfix stop". +.PP +Use \fItransport\fR_delivery_rate_delay to specify a +transport-specific override, where \fItransport\fR is the master.cf +name of the message delivery transport. +.PP +This feature is available in Postfix 2.5 and later. .SH default_delivery_slot_cost (default: 5) How often the Postfix queue manager's scheduler is allowed to preempt delivery of one message with another. @@ -904,6 +924,10 @@ disabled. The default value of 5 turns out to provide reasonable message response times while making sure the mailing-list deliveries are not extended by more than 20-25 percent even in the worst case. .PP +Use \fItransport\fR_delivery_slot_cost to specify a +transport-specific override, where \fItransport\fR is the master.cf +name of the message delivery transport. +.PP Examples: .PP .nf @@ -925,6 +949,10 @@ transport_delivery_slot_discount percent of the required amount plus transport_delivery_slot_loan still remains to be accumulated. Note that the full amount will still have to be accumulated before another preemption can take place later. +.PP +Use \fItransport\fR_delivery_slot_discount to specify a +transport-specific override, where \fItransport\fR is the master.cf +name of the message delivery transport. .SH default_delivery_slot_loan (default: 3) The default value for transport-specific _delivery_slot_loan settings. @@ -936,6 +964,10 @@ transport_delivery_slot_discount percent of the required amount plus transport_delivery_slot_loan still remains to be accumulated. Note that the full amount will still have to be accumulated before another preemption can take place later. +.PP +Use \fItransport\fR_delivery_slot_loan to specify a +transport-specific override, where \fItransport\fR is the master.cf +name of the message delivery transport. .SH default_destination_concurrency_failed_cohort_limit (default: 1) How many pseudo-cohorts must suffer connection or handshake failure before a specific destination is considered unavailable @@ -957,6 +989,12 @@ is compatible with earlier Postfix versions. The default maximal number of parallel deliveries to the same destination. This is the default limit for delivery via the \fBlmtp\fR(8), \fBpipe\fR(8), \fBsmtp\fR(8) and \fBvirtual\fR(8) delivery agents. +With per-destination recipient limit > 1, a destination is a domain, +otherwise it is a recipient. +.PP +Use \fItransport\fR_destination_concurrency_limit to specify a +transport-specific override, where \fItransport\fR is the master.cf +name of the message delivery transport. .SH default_destination_concurrency_negative_feedback (default: 1) The per-destination amount of delivery concurrency negative feedback, after a delivery completes with a connection or handshake @@ -1046,6 +1084,10 @@ This is the default limit for delivery via the \fBlmtp\fR(8), \fBpipe\fR(8), Setting this parameter to a value of 1 changes the meaning of the corresponding per-destination concurrency limit from concurrency per domain into concurrency per recipient. +.PP +Use \fItransport\fR_destination_recipient_limit to specify a +transport-specific override, where \fItransport\fR is the master.cf +name of the message delivery transport. .SH default_extra_recipient_limit (default: 1000) The default value for the extra per-transport limit imposed on the number of in-memory recipients. This extra recipient space is @@ -1053,11 +1095,19 @@ reserved for the cases when the Postfix queue manager's scheduler preempts one message with another and suddenly needs some extra recipients slots for the chosen message in order to avoid performance degradation. +.PP +Use \fItransport\fR_extra_recipient_limit to specify a +transport-specific override, where \fItransport\fR is the master.cf +name of the message delivery transport. .SH default_minimum_delivery_slots (default: 3) How many recipients a message must have in order to invoke the Postfix queue manager's scheduling algorithm at all. Messages which would never accumulate at least this many delivery slots (subject to slot cost parameter as well) are never preempted. +.PP +Use \fItransport\fR_minimum_delivery_slots to specify a +transport-specific override, where \fItransport\fR is the master.cf +name of the message delivery transport. .SH default_privs (default: nobody) The default rights used by the \fBlocal\fR(8) delivery agent for delivery to external file or command. These rights are used when delivery @@ -1142,6 +1192,10 @@ recipients. These limits take priority over the global qmgr_message_recipient_limit after the message has been assigned to the respective transports. See also default_extra_recipient_limit and qmgr_message_recipient_minimum. +.PP +Use \fItransport\fR_recipient_limit to specify a +transport-specific override, where \fItransport\fR is the master.cf +name of the message delivery transport. .SH default_recipient_refill_delay (default: 5s) The default per-transport maximum delay between recipients refills. When not all message recipients fit into the memory at once, keep loading @@ -1149,6 +1203,10 @@ more of them at least once every this many seconds. This is used to make sure the recipients are refilled in timely manner even when $default_recipient_refill_limit is too high for too slow deliveries. .PP +Use \fItransport\fR_recipient_refill_delay to specify a +transport-specific override, where \fItransport\fR is the master.cf +name of the message delivery transport. +.PP This feature is available in Postfix 2.4 and later. .SH default_recipient_refill_limit (default: 100) The default per-transport limit on the number of recipients refilled at @@ -1157,6 +1215,10 @@ loading more of them in batches of at least this many at a time. See also $default_recipient_refill_delay, which may result in recipient batches lower than this when this limit is too high for too slow deliveries. .PP +Use \fItransport\fR_recipient_refill_limit to specify a +transport-specific override, where \fItransport\fR is the master.cf +name of the message delivery transport. +.PP This feature is available in Postfix 2.4 and later. .SH default_transport (default: smtp) The default mail delivery transport and next-hop destination for @@ -1759,6 +1821,8 @@ inet_protocols = ipv4, ipv6 The initial per-destination concurrency level for parallel delivery to the same destination. This limit applies to delivery via \fBsmtp\fR(8), and via the \fBpipe\fR(8) and \fBvirtual\fR(8) delivery agents. +With per-destination recipient limit > 1, a destination is a domain, +otherwise it is a recipient. .PP Use \fItransport\fR_initial_destination_concurrency to specify a transport-specific override, where \fItransport\fR is the master.cf @@ -7417,6 +7481,12 @@ of mail deliveries and produces a mail delivery report when verbose delivery is requested with "\fBsendmail -v\fR". .PP This feature is available in Postfix 2.1 and later. +.SH transport_delivery_rate_delay (default: $default_delivery_rate_delay) +A transport-specific override for the default_recipient_refill_delay +parameter value, where \fItransport\fR is the master.cf name of +the message delivery transport. +.PP +This feature is available in Postfix 2.5 and later. .SH transport_delivery_slot_cost (default: $default_delivery_slot_cost) A transport-specific override for the default_delivery_slot_cost parameter value, where \fItransport\fR is the master.cf name of diff --git a/postfix/man/man8/oqmgr.8 b/postfix/man/man8/oqmgr.8 index eb058f738..cf32b36d3 100644 --- a/postfix/man/man8/oqmgr.8 +++ b/postfix/man/man8/oqmgr.8 @@ -224,13 +224,13 @@ failure before a specific destination is considered unavailable .IP "\fItransport\fB_destination_concurrency_failed_cohort_limit ($default_destination_concurrency_failed_cohort_limit)\fR" Idem, for delivery via the named message \fItransport\fR. .IP "\fBdefault_destination_concurrency_negative_feedback (1)\fR" -The per-destination amount of negative delivery concurrency +The per-destination amount of delivery concurrency negative feedback, after a delivery completes with a connection or handshake failure. .IP "\fItransport\fB_destination_concurrency_negative_feedback ($default_destination_concurrency_negative_feedback)\fR" Idem, for delivery via the named message \fItransport\fR. .IP "\fBdefault_destination_concurrency_positive_feedback (1)\fR" -The per-destination amount of positive delivery concurrency +The per-destination amount of delivery concurrency positive feedback, after a delivery completes without connection or handshake failure. .IP "\fItransport\fB_destination_concurrency_positive_feedback ($default_destination_concurrency_positive_feedback)\fR" @@ -271,6 +271,14 @@ Available in Postfix version 2.1 and later: .IP "\fBbounce_queue_lifetime (5d)\fR" The maximal time a bounce message is queued before it is considered undeliverable. +.PP +Available in Postfix version 2.5 and later: +.IP "\fBdefault_delivery_rate_delay (0s)\fR" +The default amount of delay that is inserted between individual +deliveries to the same destination; with per-destination recipient +limit > 1, a destination is a domain, otherwise it is a recipient. +.IP "\fItransport\fB_delivery_rate_delay $default_delivery_rate_delay +Idem, for delivery via the named message \fItransport\fR. .SH MISCELLANEOUS CONTROLS .ad .fi diff --git a/postfix/man/man8/qmgr.8 b/postfix/man/man8/qmgr.8 index e3a5bde2b..b0a5c1e63 100644 --- a/postfix/man/man8/qmgr.8 +++ b/postfix/man/man8/qmgr.8 @@ -247,13 +247,13 @@ failure before a specific destination is considered unavailable .IP "\fItransport\fB_destination_concurrency_failed_cohort_limit ($default_destination_concurrency_failed_cohort_limit)\fR" Idem, for delivery via the named message \fItransport\fR. .IP "\fBdefault_destination_concurrency_negative_feedback (1)\fR" -The per-destination amount of negative delivery concurrency +The per-destination amount of delivery concurrency negative feedback, after a delivery completes with a connection or handshake failure. .IP "\fItransport\fB_destination_concurrency_negative_feedback ($default_destination_concurrency_negative_feedback)\fR" Idem, for delivery via the named message \fItransport\fR. .IP "\fBdefault_destination_concurrency_positive_feedback (1)\fR" -The per-destination amount of positive delivery concurrency +The per-destination amount of delivery concurrency positive feedback, after a delivery completes without connection or handshake failure. .IP "\fItransport\fB_destination_concurrency_positive_feedback ($default_destination_concurrency_positive_feedback)\fR" @@ -319,6 +319,14 @@ Available in Postfix version 2.1 and later: .IP "\fBbounce_queue_lifetime (5d)\fR" The maximal time a bounce message is queued before it is considered undeliverable. +.PP +Available in Postfix version 2.5 and later: +.IP "\fBdefault_delivery_rate_delay (0s)\fR" +The default amount of delay that is inserted between individual +deliveries to the same destination; with per-destination recipient +limit > 1, a destination is a domain, otherwise it is a recipient. +.IP "\fItransport\fB_delivery_rate_delay $default_delivery_rate_delay +Idem, for delivery via the named message \fItransport\fR. .SH "MISCELLANEOUS CONTROLS" .na .nf diff --git a/postfix/mantools/postlink b/postfix/mantools/postlink index 2a842b7fc..f4e9b4409 100755 --- a/postfix/mantools/postlink +++ b/postfix/mantools/postlink @@ -339,6 +339,7 @@ while (<>) { s;\bdefault_destination_concur[-]*\n* *[]*rency_positive_feedback\b;$&;g; s;\bdefault_destination_con[-]*\n* *[]*currency_failed_cohort_limit\b;$&;g; s;\bdestination_concurrency_feedback_debug\b;$&;g; + s;\bdefault_delivery_rate_delay\b;$&;g; s;\bqmqpd_error_delay\b;$&;g; s;\bqmqpd_timeout\b;$&;g; @@ -635,7 +636,8 @@ while (<>) { s;(transport)()?(_recipient_refill_delay)\b;$2$1$3;g; s;(transport)()?(_recipient_refill_limit)\b;$2$1$3;g; s;(transport)()?(_time_limit)\b;$2$1$3;g; - s;(transport)()?(delivery_slot_discount)\b;$2$1$3;g; + s;(transport)()?(_delivery_slot_discount)\b;$2$1$3;g; + s;(transport)()?(_delivery_rate_delay)\b;$2$1$3;g; # Undo hyperlinks of manual pages with the same name as parameters. diff --git a/postfix/proto/SCHEDULER_README.html b/postfix/proto/SCHEDULER_README.html index afda19e2d..ebd67c87a 100644 --- a/postfix/proto/SCHEDULER_README.html +++ b/postfix/proto/SCHEDULER_README.html @@ -554,7 +554,7 @@ the next section.

Limitations of less-than-1 per delivery feedback

-

The delivery concurrency scheduler with less-than-1 concurrency +

The scheduler with less-than-1 concurrency feedback per delivery solves a problem with servers that have active concurrency limiters. This works only because feedback is handled in a peculiar manner: positive feedback will increment the concurrency @@ -580,9 +580,9 @@ level of about K, even though the good servers behind the load balancer are perfectly capable of handling more traffic.

This noise problem gets worse as the amount of positive feedback -per delivery gets smaller. A compromise is to avoid concurrency-dependent -positive feedback, and to use fixed less-than-1 feedback values -instead. For example, to tolerate 1 of 4 bad servers in the above +per delivery gets smaller. A compromise is to use fixed less-than-1 +positive feedback values instead of concurrency-dependent positive +feedback. For example, to tolerate 1 of 4 bad servers in the above load balancer scenario, use positive feedback of 1/4 per "good" delivery (no connect or handshake error), and use an equal or smaller amount of negative feedback per "bad" delivery. The downside of diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index fc2ea058e..f5f1771eb 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -885,6 +885,11 @@ message response times while making sure the mailing-list deliveries are not extended by more than 20-25 percent even in the worst case.

+

Use transport_delivery_slot_cost to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+

Examples:

@@ -900,6 +905,13 @@ default_delivery_slot_cost = 2 The default maximal number of parallel deliveries to the same destination. This is the default limit for delivery via the lmtp(8), pipe(8), smtp(8) and virtual(8) delivery agents. +With per-destination recipient limit > 1, a destination is a domain, +otherwise it is a recipient. +

+ +

Use transport_destination_concurrency_limit to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport.

%PARAM default_destination_recipient_limit 50 @@ -914,6 +926,11 @@ smtp(8) and virtual(8) delivery agents. the corresponding per-destination concurrency limit from concurrency per domain into concurrency per recipient.

+

Use transport_destination_recipient_limit to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+ %PARAM default_extra_recipient_limit 1000

@@ -925,6 +942,11 @@ recipients slots for the chosen message in order to avoid performance degradation.

+

Use transport_extra_recipient_limit to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+ %PARAM default_minimum_delivery_slots 3

@@ -934,6 +956,11 @@ which would never accumulate at least this many delivery slots (subject to slot cost parameter as well) are never preempted.

+

Use transport_minimum_delivery_slots to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+ %PARAM default_privs nobody

@@ -1097,6 +1124,11 @@ to the respective transports. See also default_extra_recipient_limit and qmgr_message_recipient_minimum.

+

Use transport_recipient_limit to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+ %PARAM default_recipient_refill_limit 100

@@ -1107,6 +1139,11 @@ $default_recipient_refill_delay, which may result in recipient batches lower than this when this limit is too high for too slow deliveries.

+

Use transport_recipient_refill_limit to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+

This feature is available in Postfix 2.4 and later.

%PARAM default_recipient_refill_delay 5s @@ -1119,6 +1156,11 @@ make sure the recipients are refilled in timely manner even when $default_recipient_refill_limit is too high for too slow deliveries.

+

Use transport_recipient_refill_delay to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+

This feature is available in Postfix 2.4 and later.

%PARAM default_transport smtp @@ -1805,6 +1847,8 @@ inet_protocols = ipv4, ipv6 The initial per-destination concurrency level for parallel delivery to the same destination. This limit applies to delivery via smtp(8), and via the pipe(8) and virtual(8) delivery agents. +With per-destination recipient limit > 1, a destination is a domain, +otherwise it is a recipient.

Use transport_initial_destination_concurrency to specify @@ -6566,6 +6610,11 @@ Note that the full amount will still have to be accumulated before another preemption can take place later.

+

Use transport_delivery_slot_discount to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+ %PARAM default_delivery_slot_loan 3

@@ -6583,6 +6632,11 @@ Note that the full amount will still have to be accumulated before another preemption can take place later.

+

Use transport_delivery_slot_loan to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+ %CLASS verp VERP Support

@@ -10931,3 +10985,36 @@ parameter value, where transport is the master.cf name of the message delivery transport.

This feature is available in Postfix 2.4 and later.

+ +%PARAM default_delivery_rate_delay 0s + +

The default amount of delay that is inserted between individual +deliveries to the same destination; with per-destination recipient +limit > 1, a destination is a domain, otherwise it is a recipient. +

+ +

To enable the delay, specify a non-zero time value (an integral +value plus an optional one-letter suffix that specifies the time +unit).

+ +

Time units: s (seconds), m (minutes), h (hours), d (days), w +(weeks). The default time unit is s (seconds).

+ +

NOTE: the delay is enforced by the queue manager. The delay +timer state does not survive "postfix reload" or "postfix stop". +

+ +

Use transport_delivery_rate_delay to specify a +transport-specific override, where transport is the master.cf +name of the message delivery transport. +

+ +

This feature is available in Postfix 2.5 and later.

+ +%PARAM transport_delivery_rate_delay $default_delivery_rate_delay + +

A transport-specific override for the default_recipient_refill_delay +parameter value, where transport is the master.cf name of +the message delivery transport.

+ +

This feature is available in Postfix 2.5 and later.

diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index 3f82ac820..cb46de241 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -2861,6 +2861,11 @@ extern int var_conc_cohort_limit; #define DEF_CONC_FDBACK_DEBUG 0 extern bool var_conc_feedback_debug; +#define VAR_DEST_RATE_DELAY "default_delivery_rate_delay" +#define _DEST_RATE_DELAY "_delivery_rate_delay" +#define DEF_DEST_RATE_DELAY "0s" +extern int var_dest_rate_delay; + /* LICENSE /* .ad /* .fi diff --git a/postfix/src/oqmgr/qmgr.c b/postfix/src/oqmgr/qmgr.c index 6d82a7fe9..fcc95a2f5 100644 --- a/postfix/src/oqmgr/qmgr.c +++ b/postfix/src/oqmgr/qmgr.c @@ -194,13 +194,13 @@ /* .IP "\fItransport\fB_destination_concurrency_failed_cohort_limit ($default_destination_concurrency_failed_cohort_limit)\fR" /* Idem, for delivery via the named message \fItransport\fR. /* .IP "\fBdefault_destination_concurrency_negative_feedback (1)\fR" -/* The per-destination amount of negative delivery concurrency +/* The per-destination amount of delivery concurrency negative /* feedback, after a delivery completes with a connection or handshake /* failure. /* .IP "\fItransport\fB_destination_concurrency_negative_feedback ($default_destination_concurrency_negative_feedback)\fR" /* Idem, for delivery via the named message \fItransport\fR. /* .IP "\fBdefault_destination_concurrency_positive_feedback (1)\fR" -/* The per-destination amount of positive delivery concurrency +/* The per-destination amount of delivery concurrency positive /* feedback, after a delivery completes without connection or handshake /* failure. /* .IP "\fItransport\fB_destination_concurrency_positive_feedback ($default_destination_concurrency_positive_feedback)\fR" @@ -237,6 +237,14 @@ /* .IP "\fBbounce_queue_lifetime (5d)\fR" /* The maximal time a bounce message is queued before it is considered /* undeliverable. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBdefault_delivery_rate_delay (0s)\fR" +/* The default amount of delay that is inserted between individual +/* deliveries to the same destination; with per-destination recipient +/* limit > 1, a destination is a domain, otherwise it is a recipient. +/* .IP "\fItransport\fB_delivery_rate_delay $default_delivery_rate_delay +/* Idem, for delivery via the named message \fItransport\fR. /* .SH MISCELLANEOUS CONTROLS /* .ad /* .fi @@ -362,6 +370,7 @@ char *var_conc_pos_feedback; char *var_conc_neg_feedback; int var_conc_cohort_limit; int var_conc_feedback_debug; +int var_dest_rate_delay; static QMGR_SCAN *qmgr_scans[2]; @@ -601,6 +610,7 @@ int main(int argc, char **argv) VAR_DSN_QUEUE_TIME, DEF_DSN_QUEUE_TIME, &var_dsn_queue_time, 0, 8640000, VAR_XPORT_RETRY_TIME, DEF_XPORT_RETRY_TIME, &var_transport_retry_time, 1, 0, VAR_QMGR_CLOG_WARN_TIME, DEF_QMGR_CLOG_WARN_TIME, &var_qmgr_clog_warn_time, 0, 0, + VAR_DEST_RATE_DELAY, DEF_DEST_RATE_DELAY, &var_dest_rate_delay, 0, 0, 0, }; static CONFIG_INT_TABLE int_table[] = { diff --git a/postfix/src/oqmgr/qmgr.h b/postfix/src/oqmgr/qmgr.h index 1cc8bedab..837c04866 100644 --- a/postfix/src/oqmgr/qmgr.h +++ b/postfix/src/oqmgr/qmgr.h @@ -161,6 +161,7 @@ struct QMGR_TRANSPORT { QMGR_FEEDBACK pos_feedback; /* positive feedback control */ QMGR_FEEDBACK neg_feedback; /* negative feedback control */ int fail_cohort_limit; /* flow shutdown control */ + int rate_delay; /* suspend per delivery */ }; #define QMGR_TRANSPORT_STAT_DEAD (1<<1) @@ -219,8 +220,36 @@ extern void qmgr_queue_done(QMGR_QUEUE *); extern void qmgr_queue_throttle(QMGR_QUEUE *, DSN *); extern void qmgr_queue_unthrottle(QMGR_QUEUE *); extern QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *, const char *); +extern void qmgr_queue_suspend(QMGR_QUEUE *, int); -#define QMGR_QUEUE_THROTTLED(q) ((q)->window <= 0) + /* + * Exclusive queue states. Originally there were only two: "throttled" and + * "not throttled". It was natural to encode these in the queue window size. + * After 10 years it's not practical to rip out all the working code and + * change representations, so we just clean up the names a little. + * + * Note: only the "ready" state can reach every state (including itself); + * non-ready states can reach only the "ready" state. Other transitions are + * forbidden, because they would result in dangling event handlers. + */ +#define QMGR_QUEUE_STAT_THROTTLED 0 /* back-off timer */ +#define QMGR_QUEUE_STAT_SUSPENDED -1 /* voluntary delay timer */ +#define QMGR_QUEUE_STAT_SAVED -2 /* delayed cleanup timer */ +#define QMGR_QUEUE_STAT_BAD -3 /* can't happen */ + +#define QMGR_QUEUE_READY(q) ((q)->window > 0) +#define QMGR_QUEUE_THROTTLED(q) ((q)->window == QMGR_QUEUE_STAT_THROTTLED) +#define QMGR_QUEUE_SUSPENDED(q) ((q)->window == QMGR_QUEUE_STAT_SUSPENDED) +#define QMGR_QUEUE_SAVED(q) ((q)->window == QMGR_QUEUE_STAT_SAVED) +#define QMGR_QUEUE_BAD(q) ((q)->window <= QMGR_QUEUE_STAT_BAD) + +#define QMGR_QUEUE_STATUS(q) ( \ + QMGR_QUEUE_READY(q) ? "ready" : \ + QMGR_QUEUE_THROTTLED(q) ? "throttled" : \ + QMGR_QUEUE_SUSPENDED(q) ? "suspended" : \ + QMGR_QUEUE_SAVED(q) ? "saved" : \ + "invalid queue status" \ + ) /* * Structure of one next-hop queue entry. In order to save some copying diff --git a/postfix/src/oqmgr/qmgr_deliver.c b/postfix/src/oqmgr/qmgr_deliver.c index b616797cc..47c75d7eb 100644 --- a/postfix/src/oqmgr/qmgr_deliver.c +++ b/postfix/src/oqmgr/qmgr_deliver.c @@ -312,9 +312,9 @@ static void qmgr_deliver_update(int unused_event, char *context) if (VSTRING_LEN(dsb->reason) == 0) vstring_strcpy(dsb->reason, "unknown error"); vstring_prepend(dsb->reason, SUSPENDED, sizeof(SUSPENDED) - 1); - if (queue->window > 0) { + if (QMGR_QUEUE_READY(queue)) { qmgr_queue_throttle(queue, DSN_FROM_DSN_BUF(dsb)); - if (queue->window == 0) + if (QMGR_QUEUE_THROTTLED(queue)) qmgr_defer_todo(queue, &dsb->dsn); } } diff --git a/postfix/src/oqmgr/qmgr_enable.c b/postfix/src/oqmgr/qmgr_enable.c index 5f0c3841b..a35e46e48 100644 --- a/postfix/src/oqmgr/qmgr_enable.c +++ b/postfix/src/oqmgr/qmgr_enable.c @@ -97,11 +97,11 @@ void qmgr_enable_transport(QMGR_TRANSPORT *transport) void qmgr_enable_queue(QMGR_QUEUE *queue) { - if (queue->window == 0) { + if (QMGR_QUEUE_THROTTLED(queue)) { if (msg_verbose) msg_info("enable site %s/%s", queue->transport->name, queue->name); qmgr_queue_unthrottle(queue); } - if (queue->todo.next == 0 && queue->busy.next == 0) + if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0) qmgr_queue_done(queue); } diff --git a/postfix/src/oqmgr/qmgr_entry.c b/postfix/src/oqmgr/qmgr_entry.c index fda4e5790..0f0ac1840 100644 --- a/postfix/src/oqmgr/qmgr_entry.c +++ b/postfix/src/oqmgr/qmgr_entry.c @@ -212,14 +212,16 @@ void qmgr_entry_move_todo(QMGR_QUEUE *dst, QMGR_ENTRY *entry) void qmgr_entry_done(QMGR_ENTRY *entry, int which) { + const char *myname = "qmgr_entry_done"; QMGR_QUEUE *queue = entry->queue; QMGR_MESSAGE *message = entry->message; + QMGR_TRANSPORT *transport = queue->transport; /* * Take this entry off the in-core queue. */ if (entry->stream != 0) - msg_panic("qmgr_entry_done: file is open"); + msg_panic("%s: file is open", myname); if (which == QMGR_QUEUE_BUSY) { QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry); queue->busy_refcount--; @@ -227,7 +229,7 @@ void qmgr_entry_done(QMGR_ENTRY *entry, int which) QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry); queue->todo_refcount--; } else { - msg_panic("qmgr_entry_done: bad queue spec: %d", which); + msg_panic("%s: bad queue spec: %d", myname, which); } /* @@ -242,7 +244,21 @@ void qmgr_entry_done(QMGR_ENTRY *entry, int which) /* * Maintain back-to-back delivery status. */ - queue->last_done = event_time(); + if (which == QMGR_QUEUE_BUSY) + queue->last_done = event_time(); + + /* + * Suspend a rate-limited queue, so that mail trickles out. + */ + if (which == QMGR_QUEUE_BUSY && transport->rate_delay > 0) { + if (queue->window > 1) + msg_panic("%s: queue %s/%s: window %d > 1 on rate-limited service", + myname, transport->name, queue->name, queue->window); + if (QMGR_QUEUE_THROTTLED(queue)) /* XXX */ + qmgr_queue_unthrottle(queue); + if (QMGR_QUEUE_READY(queue)) + qmgr_queue_suspend(queue, transport->rate_delay); + } /* * When the in-core queue for this site is empty and when this site is @@ -253,9 +269,9 @@ void qmgr_entry_done(QMGR_ENTRY *entry, int which) * See also: qmgr_entry_move_todo(). */ if (queue->todo.next == 0 && queue->busy.next == 0) { - if (queue->window == 0 && qmgr_queue_count > 2 * var_qmgr_rcpt_limit) + if (QMGR_QUEUE_THROTTLED(queue) && qmgr_queue_count > 2 * var_qmgr_rcpt_limit) qmgr_queue_unthrottle(queue); - if (queue->window > 0) + if (QMGR_QUEUE_READY(queue)) qmgr_queue_done(queue); } @@ -293,7 +309,7 @@ QMGR_ENTRY *qmgr_entry_create(QMGR_QUEUE *queue, QMGR_MESSAGE *message) /* * Sanity check. */ - if (queue->window == 0) + if (QMGR_QUEUE_THROTTLED(queue)) msg_panic("qmgr_entry_create: dead queue: %s", queue->name); /* diff --git a/postfix/src/oqmgr/qmgr_queue.c b/postfix/src/oqmgr/qmgr_queue.c index 385def4a2..22bf0921c 100644 --- a/postfix/src/oqmgr/qmgr_queue.c +++ b/postfix/src/oqmgr/qmgr_queue.c @@ -29,6 +29,10 @@ /* /* void qmgr_queue_unthrottle(queue) /* QMGR_QUEUE *queue; +/* +/* void qmgr_queue_suspend(queue, delay) +/* QMGR_QUEUE *queue; +/* int delay; /* DESCRIPTION /* These routines add/delete/manipulate per-destination queues. /* Each queue corresponds to a specific transport and destination. @@ -67,8 +71,11 @@ /* provided that it does not exceed the destination concurrency /* limit specified for the transport. This routine implements /* "slow open" mode, and eliminates the "thundering herd" problem. +/* +/* qmgr_queue_suspend() suspends delivery for this destination +/* briefly. /* DIAGNOSTICS -/* None +/* Panic: consistency check failure. /* LICENSE /* .ad /* .fi @@ -118,6 +125,53 @@ int qmgr_queue_count; myname, queue->name, queue->transport->dest_concurrency_limit, \ queue->window, queue->success, queue->failure, queue->fail_cohorts); +/* qmgr_queue_resume - resume delivery to destination */ + +static void qmgr_queue_resume(int event, char *context) +{ + QMGR_QUEUE *queue = (QMGR_QUEUE *) context; + const char *myname = "qmgr_queue_resume"; + + /* + * Sanity checks. + */ + if (!QMGR_QUEUE_SUSPENDED(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + + /* + * We can't simply force delivery on this queue: the transport's pending + * count may already be maxed out, and there may be other constraints + * that definitely should be none of our business. The best we can do is + * to play by the same rules as everyone else: trigger *some* delivery + * via qmgr_active_drain() and let round-robin selection work for us. + */ + queue->window = 1; + if (queue->todo_refcount > 0) + qmgr_active_drain(); +} + +/* qmgr_queue_suspend - briefly suspend a destination */ + +void qmgr_queue_suspend(QMGR_QUEUE *queue, int delay) +{ + const char *myname = "qmgr_queue_suspend"; + + /* + * Sanity checks. + */ + if (!QMGR_QUEUE_READY(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + if (queue->busy_refcount > 0) + msg_panic("%s: queue is busy", myname); + + /* + * Set the queue status to "suspended". No-one is supposed to remove a + * queue in suspended state. + */ + queue->window = QMGR_QUEUE_STAT_SUSPENDED; + event_request_timer(qmgr_queue_resume, (char *) queue, delay); +} + /* qmgr_queue_unthrottle_wrapper - in case (char *) != (struct *) */ static void qmgr_queue_unthrottle_wrapper(int unused_event, char *context) @@ -130,7 +184,7 @@ static void qmgr_queue_unthrottle_wrapper(int unused_event, char *context) * this in-core queue when it is empty and when this site is not dead. */ qmgr_queue_unthrottle(queue); - if (queue->window > 0 && queue->todo.next == 0 && queue->busy.next == 0) + if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0) qmgr_queue_done(queue); } @@ -145,6 +199,12 @@ void qmgr_queue_unthrottle(QMGR_QUEUE *queue) if (msg_verbose) msg_info("%s: queue %s", myname, queue->name); + /* + * Sanity checks. + */ + if (!QMGR_QUEUE_THROTTLED(queue) && !QMGR_QUEUE_READY(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + /* * Don't restart the negative feedback hysteresis cycle with every * positive feedback. Restart it only when we make a positive concurrency @@ -157,7 +217,7 @@ void qmgr_queue_unthrottle(QMGR_QUEUE *queue) /* * Special case when this site was dead. */ - if (queue->window == 0) { + if (QMGR_QUEUE_THROTTLED(queue)) { event_cancel_timer(qmgr_queue_unthrottle_wrapper, (char *) queue); if (queue->dsn == 0) msg_panic("%s: queue %s: window 0 status 0", myname, queue->name); @@ -219,6 +279,8 @@ void qmgr_queue_throttle(QMGR_QUEUE *queue, DSN *dsn) /* * Sanity checks. */ + if (!QMGR_QUEUE_READY(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); if (queue->dsn) msg_panic("%s: queue %s: spurious reason %s", myname, queue->name, queue->dsn->reason); @@ -238,7 +300,7 @@ void qmgr_queue_throttle(QMGR_QUEUE *queue, DSN *dsn) * This queue is declared dead after a configurable number of * pseudo-cohort failures. */ - if (queue->window > 0) { + if (QMGR_QUEUE_READY(queue)) { queue->fail_cohorts += 1.0 / queue->window; if (transport->fail_cohort_limit > 0 && queue->fail_cohorts >= transport->fail_cohort_limit) @@ -254,7 +316,7 @@ void qmgr_queue_throttle(QMGR_QUEUE *queue, DSN *dsn) * Even after reaching 1, we maintain the negative hysteresis cycle so that * negative feedback can cancel out positive feedback. */ - if (queue->window > 0) { + if (QMGR_QUEUE_READY(queue)) { feedback = QMGR_FEEDBACK_VAL(transport->neg_feedback, queue->window); QMGR_LOG_FEEDBACK(feedback); queue->failure -= feedback; @@ -272,7 +334,7 @@ void qmgr_queue_throttle(QMGR_QUEUE *queue, DSN *dsn) /* * Special case for a site that just was declared dead. */ - if (queue->window == 0) { + if (QMGR_QUEUE_THROTTLED(queue)) { queue->dsn = DSN_COPY(dsn); event_request_timer(qmgr_queue_unthrottle_wrapper, (char *) queue, var_min_backoff_time); @@ -318,8 +380,8 @@ void qmgr_queue_done(QMGR_QUEUE *queue) queue->busy_refcount + queue->todo_refcount); if (queue->todo.next || queue->busy.next) msg_panic("%s: queue not empty: %s", myname, queue->name); - if (queue->window <= 0) - msg_panic("%s: window %d", myname, queue->window); + if (!QMGR_QUEUE_READY(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); if (queue->dsn) msg_panic("%s: queue %s: spurious reason %s", myname, queue->name, queue->dsn->reason); diff --git a/postfix/src/oqmgr/qmgr_transport.c b/postfix/src/oqmgr/qmgr_transport.c index cedb8a815..aac9dc35c 100644 --- a/postfix/src/oqmgr/qmgr_transport.c +++ b/postfix/src/oqmgr/qmgr_transport.c @@ -384,7 +384,12 @@ QMGR_TRANSPORT *qmgr_transport_create(const char *name) transport->init_dest_concurrency = get_mail_conf_int2(name, _INIT_DEST_CON, var_init_dest_concurrency, 1, 0); + transport->rate_delay = get_mail_conf_time2(name, _DEST_RATE_DELAY, + var_dest_rate_delay, + 's', 0, 0); + if (transport->rate_delay > 0) + transport->dest_concurrency_limit = 1; if (transport->dest_concurrency_limit != 0 && transport->dest_concurrency_limit < transport->init_dest_concurrency) transport->init_dest_concurrency = transport->dest_concurrency_limit; diff --git a/postfix/src/postconf/auto.awk b/postfix/src/postconf/auto.awk index 0b7c00659..18f60c797 100644 --- a/postfix/src/postconf/auto.awk +++ b/postfix/src/postconf/auto.awk @@ -8,6 +8,7 @@ BEGIN { vars["destination_concurrency_positive_feedback"] = "default_destination_concurrency_positive_feedback" vars["destination_recipient_limit"] = "default_destination_recipient_limit" vars["initial_destination_concurrency"] = "initial_destination_concurrency" + vars["delivery_rate_delay"] = "default_delivery_rate_delay" # auto_table.h diff --git a/postfix/src/qmgr/qmgr.c b/postfix/src/qmgr/qmgr.c index 828532cb9..37fcc949b 100644 --- a/postfix/src/qmgr/qmgr.c +++ b/postfix/src/qmgr/qmgr.c @@ -217,13 +217,13 @@ /* .IP "\fItransport\fB_destination_concurrency_failed_cohort_limit ($default_destination_concurrency_failed_cohort_limit)\fR" /* Idem, for delivery via the named message \fItransport\fR. /* .IP "\fBdefault_destination_concurrency_negative_feedback (1)\fR" -/* The per-destination amount of negative delivery concurrency +/* The per-destination amount of delivery concurrency negative /* feedback, after a delivery completes with a connection or handshake /* failure. /* .IP "\fItransport\fB_destination_concurrency_negative_feedback ($default_destination_concurrency_negative_feedback)\fR" /* Idem, for delivery via the named message \fItransport\fR. /* .IP "\fBdefault_destination_concurrency_positive_feedback (1)\fR" -/* The per-destination amount of positive delivery concurrency +/* The per-destination amount of delivery concurrency positive /* feedback, after a delivery completes without connection or handshake /* failure. /* .IP "\fItransport\fB_destination_concurrency_positive_feedback ($default_destination_concurrency_positive_feedback)\fR" @@ -283,6 +283,14 @@ /* .IP "\fBbounce_queue_lifetime (5d)\fR" /* The maximal time a bounce message is queued before it is considered /* undeliverable. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBdefault_delivery_rate_delay (0s)\fR" +/* The default amount of delay that is inserted between individual +/* deliveries to the same destination; with per-destination recipient +/* limit > 1, a destination is a domain, otherwise it is a recipient. +/* .IP "\fItransport\fB_delivery_rate_delay $default_delivery_rate_delay +/* Idem, for delivery via the named message \fItransport\fR. /* MISCELLANEOUS CONTROLS /* .ad /* .fi @@ -422,6 +430,7 @@ char *var_conc_pos_feedback; char *var_conc_neg_feedback; int var_conc_cohort_limit; int var_conc_feedback_debug; +int var_dest_rate_delay; static QMGR_SCAN *qmgr_scans[2]; @@ -669,6 +678,7 @@ int main(int argc, char **argv) VAR_XPORT_RETRY_TIME, DEF_XPORT_RETRY_TIME, &var_transport_retry_time, 1, 0, VAR_QMGR_CLOG_WARN_TIME, DEF_QMGR_CLOG_WARN_TIME, &var_qmgr_clog_warn_time, 0, 0, VAR_XPORT_REFILL_DELAY, DEF_XPORT_REFILL_DELAY, &var_xport_refill_delay, 1, 0, + VAR_DEST_RATE_DELAY, DEF_DEST_RATE_DELAY, &var_dest_rate_delay, 0, 0, 0, }; static CONFIG_INT_TABLE int_table[] = { diff --git a/postfix/src/qmgr/qmgr.h b/postfix/src/qmgr/qmgr.h index f5c14e73b..3aa5fb5ec 100644 --- a/postfix/src/qmgr/qmgr.h +++ b/postfix/src/qmgr/qmgr.h @@ -202,6 +202,7 @@ struct QMGR_TRANSPORT { QMGR_FEEDBACK pos_feedback; /* positive feedback control */ QMGR_FEEDBACK neg_feedback; /* negative feedback control */ int fail_cohort_limit; /* flow shutdown control */ + int rate_delay; /* suspend per delivery */ }; #define QMGR_TRANSPORT_STAT_DEAD (1<<1) @@ -258,8 +259,36 @@ extern void qmgr_queue_done(QMGR_QUEUE *); extern void qmgr_queue_throttle(QMGR_QUEUE *, DSN *); extern void qmgr_queue_unthrottle(QMGR_QUEUE *); extern QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *, const char *); +extern void qmgr_queue_suspend(QMGR_QUEUE *, int); -#define QMGR_QUEUE_THROTTLED(q) ((q)->window <= 0) + /* + * Exclusive queue states. Originally there were only two: "throttled" and + * "not throttled". It was natural to encode these in the queue window size. + * After 10 years it's not practical to rip out all the working code and + * change representations, so we just clean up the names a little. + * + * Note: only the "ready" state can reach every state (including itself); + * non-ready states can reach only the "ready" state. Other transitions are + * forbidden, because they would result in dangling event handlers. + */ +#define QMGR_QUEUE_STAT_THROTTLED 0 /* back-off timer */ +#define QMGR_QUEUE_STAT_SUSPENDED -1 /* voluntary delay timer */ +#define QMGR_QUEUE_STAT_SAVED -2 /* delayed cleanup timer */ +#define QMGR_QUEUE_STAT_BAD -3 /* can't happen */ + +#define QMGR_QUEUE_READY(q) ((q)->window > 0) +#define QMGR_QUEUE_THROTTLED(q) ((q)->window == QMGR_QUEUE_STAT_THROTTLED) +#define QMGR_QUEUE_SUSPENDED(q) ((q)->window == QMGR_QUEUE_STAT_SUSPENDED) +#define QMGR_QUEUE_SAVED(q) ((q)->window == QMGR_QUEUE_STAT_SAVED) +#define QMGR_QUEUE_BAD(q) ((q)->window <= QMGR_QUEUE_STAT_BAD) + +#define QMGR_QUEUE_STATUS(q) ( \ + QMGR_QUEUE_READY(q) ? "ready" : \ + QMGR_QUEUE_THROTTLED(q) ? "throttled" : \ + QMGR_QUEUE_SUSPENDED(q) ? "suspended" : \ + QMGR_QUEUE_SAVED(q) ? "saved" : \ + "invalid queue status" \ + ) /* * Structure of one next-hop queue entry. In order to save some copying diff --git a/postfix/src/qmgr/qmgr_deliver.c b/postfix/src/qmgr/qmgr_deliver.c index 6a6c5e7ec..668c92449 100644 --- a/postfix/src/qmgr/qmgr_deliver.c +++ b/postfix/src/qmgr/qmgr_deliver.c @@ -317,9 +317,9 @@ static void qmgr_deliver_update(int unused_event, char *context) if (VSTRING_LEN(dsb->reason) == 0) vstring_strcpy(dsb->reason, "unknown error"); vstring_prepend(dsb->reason, SUSPENDED, sizeof(SUSPENDED) - 1); - if (queue->window > 0) { + if (QMGR_QUEUE_READY(queue)) { qmgr_queue_throttle(queue, DSN_FROM_DSN_BUF(dsb)); - if (queue->window == 0) + if (QMGR_QUEUE_THROTTLED(queue)) qmgr_defer_todo(queue, &dsb->dsn); } } diff --git a/postfix/src/qmgr/qmgr_enable.c b/postfix/src/qmgr/qmgr_enable.c index 5f0c3841b..a35e46e48 100644 --- a/postfix/src/qmgr/qmgr_enable.c +++ b/postfix/src/qmgr/qmgr_enable.c @@ -97,11 +97,11 @@ void qmgr_enable_transport(QMGR_TRANSPORT *transport) void qmgr_enable_queue(QMGR_QUEUE *queue) { - if (queue->window == 0) { + if (QMGR_QUEUE_THROTTLED(queue)) { if (msg_verbose) msg_info("enable site %s/%s", queue->transport->name, queue->name); qmgr_queue_unthrottle(queue); } - if (queue->todo.next == 0 && queue->busy.next == 0) + if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0) qmgr_queue_done(queue); } diff --git a/postfix/src/qmgr/qmgr_entry.c b/postfix/src/qmgr/qmgr_entry.c index 2a332f3a3..82228d59c 100644 --- a/postfix/src/qmgr/qmgr_entry.c +++ b/postfix/src/qmgr/qmgr_entry.c @@ -186,10 +186,10 @@ void qmgr_entry_unselect(QMGR_ENTRY *entry) QMGR_QUEUE *queue = entry->queue; /* - * Move the entry back to the todo lists. In case of the peer list, - * put it back to the beginning, so the select()/unselect() does - * not reorder entries. We use this in qmgr_message_assign() - * to put recipients into existing entries when possible. + * Move the entry back to the todo lists. In case of the peer list, put + * it back to the beginning, so the select()/unselect() does not reorder + * entries. We use this in qmgr_message_assign() to put recipients into + * existing entries when possible. */ QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry, queue_peers); queue->busy_refcount--; @@ -249,6 +249,7 @@ void qmgr_entry_move_todo(QMGR_QUEUE *dst_queue, QMGR_ENTRY *entry) void qmgr_entry_done(QMGR_ENTRY *entry, int which) { + const char *myname = "qmgr_entry_done"; QMGR_QUEUE *queue = entry->queue; QMGR_MESSAGE *message = entry->message; QMGR_PEER *peer = entry->peer; @@ -259,7 +260,7 @@ void qmgr_entry_done(QMGR_ENTRY *entry, int which) * Take this entry off the in-core queue. */ if (entry->stream != 0) - msg_panic("qmgr_entry_done: file is open"); + msg_panic("%s: file is open", myname); if (which == QMGR_QUEUE_BUSY) { QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry, queue_peers); queue->busy_refcount--; @@ -269,7 +270,7 @@ void qmgr_entry_done(QMGR_ENTRY *entry, int which) QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry, queue_peers); queue->todo_refcount--; } else { - msg_panic("qmgr_entry_done: bad queue spec: %d", which); + msg_panic("%s: bad queue spec: %d", myname, which); } /* @@ -319,7 +320,7 @@ void qmgr_entry_done(QMGR_ENTRY *entry, int which) transport->job_current = transport->job_list.next; transport->candidate_cache_current = 0; } - if (queue->window > queue->busy_refcount || queue->window == 0) + if (queue->window > queue->busy_refcount || QMGR_QUEUE_THROTTLED(queue)) queue->blocker_tag = 0; } @@ -334,18 +335,32 @@ void qmgr_entry_done(QMGR_ENTRY *entry, int which) /* * Maintain back-to-back delivery status. */ - queue->last_done = event_time(); + if (which == QMGR_QUEUE_BUSY) + queue->last_done = event_time(); + + /* + * Suspend a rate-limited queue, so that mail trickles out. + */ + if (which == QMGR_QUEUE_BUSY && transport->rate_delay > 0) { + if (queue->window > 1) + msg_panic("%s: queue %s/%s: window %d > 1 on rate-limited service", + myname, transport->name, queue->name, queue->window); + if (QMGR_QUEUE_THROTTLED(queue)) /* XXX */ + qmgr_queue_unthrottle(queue); + if (QMGR_QUEUE_READY(queue)) + qmgr_queue_suspend(queue, transport->rate_delay); + } /* * When the in-core queue for this site is empty and when this site is - * not dead, discard the in-core queue. When this site is dead, but the - * number of in-core queues exceeds some threshold, get rid of this - * in-core queue anyway, in order to avoid running out of memory. + * not dead or suspended, discard the in-core queue. When this site is + * dead, but the number of in-core queues exceeds some threshold, get rid + * of this in-core queue anyway, in order to avoid running out of memory. */ if (queue->todo.next == 0 && queue->busy.next == 0) { - if (queue->window == 0 && qmgr_queue_count > 2 * var_qmgr_rcpt_limit) + if (QMGR_QUEUE_THROTTLED(queue) && qmgr_queue_count > 2 * var_qmgr_rcpt_limit) qmgr_queue_unthrottle(queue); - if (queue->window > 0) + if (QMGR_QUEUE_READY(queue)) qmgr_queue_done(queue); } @@ -368,7 +383,7 @@ QMGR_ENTRY *qmgr_entry_create(QMGR_PEER *peer, QMGR_MESSAGE *message) /* * Sanity check. */ - if (queue->window == 0) + if (QMGR_QUEUE_THROTTLED(queue)) msg_panic("qmgr_entry_create: dead queue: %s", queue->name); /* diff --git a/postfix/src/qmgr/qmgr_queue.c b/postfix/src/qmgr/qmgr_queue.c index 110c168b7..f96e853b4 100644 --- a/postfix/src/qmgr/qmgr_queue.c +++ b/postfix/src/qmgr/qmgr_queue.c @@ -26,6 +26,10 @@ /* /* void qmgr_queue_unthrottle(queue) /* QMGR_QUEUE *queue; +/* +/* void qmgr_queue_suspend(queue, delay) +/* QMGR_QUEUE *queue; +/* int delay; /* DESCRIPTION /* These routines add/delete/manipulate per-destination queues. /* Each queue corresponds to a specific transport and destination. @@ -60,6 +64,9 @@ /* provided that it does not exceed the destination concurrency /* limit specified for the transport. This routine implements /* "slow open" mode, and eliminates the "thundering herd" problem. +/* +/* qmgr_queue_suspend() suspends delivery for this destination +/* briefly. /* DIAGNOSTICS /* Panic: consistency check failure. /* LICENSE @@ -120,6 +127,53 @@ int qmgr_queue_count; myname, queue->name, queue->transport->dest_concurrency_limit, \ queue->window, queue->success, queue->failure, queue->fail_cohorts); +/* qmgr_queue_resume - resume delivery to destination */ + +static void qmgr_queue_resume(int event, char *context) +{ + QMGR_QUEUE *queue = (QMGR_QUEUE *) context; + const char *myname = "qmgr_queue_resume"; + + /* + * Sanity checks. + */ + if (!QMGR_QUEUE_SUSPENDED(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + + /* + * We can't simply force delivery on this queue: the transport's pending + * count may already be maxed out, and there may be other constraints + * that definitely should be none of our business. The best we can do is + * to play by the same rules as everyone else: trigger *some* delivery + * via qmgr_active_drain() and let round-robin selection work for us. + */ + queue->window = 1; + if (queue->todo_refcount > 0) + qmgr_active_drain(); +} + +/* qmgr_queue_suspend - briefly suspend a destination */ + +void qmgr_queue_suspend(QMGR_QUEUE *queue, int delay) +{ + const char *myname = "qmgr_queue_suspend"; + + /* + * Sanity checks. + */ + if (!QMGR_QUEUE_READY(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + if (queue->busy_refcount > 0) + msg_panic("%s: queue is busy", myname); + + /* + * Set the queue status to "suspended". No-one is supposed to remove a + * queue in suspended state. + */ + queue->window = QMGR_QUEUE_STAT_SUSPENDED; + event_request_timer(qmgr_queue_resume, (char *) queue, delay); +} + /* qmgr_queue_unthrottle_wrapper - in case (char *) != (struct *) */ static void qmgr_queue_unthrottle_wrapper(int unused_event, char *context) @@ -132,7 +186,7 @@ static void qmgr_queue_unthrottle_wrapper(int unused_event, char *context) * this in-core queue when it is empty and when this site is not dead. */ qmgr_queue_unthrottle(queue); - if (queue->window > 0 && queue->todo.next == 0 && queue->busy.next == 0) + if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0) qmgr_queue_done(queue); } @@ -147,6 +201,12 @@ void qmgr_queue_unthrottle(QMGR_QUEUE *queue) if (msg_verbose) msg_info("%s: queue %s", myname, queue->name); + /* + * Sanity checks. + */ + if (!QMGR_QUEUE_READY(queue) && !QMGR_QUEUE_THROTTLED(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + /* * Don't restart the negative feedback hysteresis cycle with every * positive feedback. Restart it only when we make a positive concurrency @@ -159,7 +219,7 @@ void qmgr_queue_unthrottle(QMGR_QUEUE *queue) /* * Special case when this site was dead. */ - if (queue->window == 0) { + if (QMGR_QUEUE_THROTTLED(queue)) { event_cancel_timer(qmgr_queue_unthrottle_wrapper, (char *) queue); if (queue->dsn == 0) msg_panic("%s: queue %s: window 0 status 0", myname, queue->name); @@ -221,6 +281,8 @@ void qmgr_queue_throttle(QMGR_QUEUE *queue, DSN *dsn) /* * Sanity checks. */ + if (!QMGR_QUEUE_READY(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); if (queue->dsn) msg_panic("%s: queue %s: spurious reason %s", myname, queue->name, queue->dsn->reason); @@ -240,7 +302,7 @@ void qmgr_queue_throttle(QMGR_QUEUE *queue, DSN *dsn) * This queue is declared dead after a configurable number of * pseudo-cohort failures. */ - if (queue->window > 0) { + if (QMGR_QUEUE_READY(queue)) { queue->fail_cohorts += 1.0 / queue->window; if (transport->fail_cohort_limit > 0 && queue->fail_cohorts >= transport->fail_cohort_limit) @@ -256,7 +318,7 @@ void qmgr_queue_throttle(QMGR_QUEUE *queue, DSN *dsn) * Even after reaching 1, we maintain the negative hysteresis cycle so that * negative feedback can cancel out positive feedback. */ - if (queue->window > 0) { + if (QMGR_QUEUE_READY(queue)) { feedback = QMGR_FEEDBACK_VAL(transport->neg_feedback, queue->window); QMGR_LOG_FEEDBACK(feedback); queue->failure -= feedback; @@ -274,7 +336,7 @@ void qmgr_queue_throttle(QMGR_QUEUE *queue, DSN *dsn) /* * Special case for a site that just was declared dead. */ - if (queue->window == 0) { + if (QMGR_QUEUE_THROTTLED(queue)) { queue->dsn = DSN_COPY(dsn); event_request_timer(qmgr_queue_unthrottle_wrapper, (char *) queue, var_min_backoff_time); @@ -299,8 +361,8 @@ void qmgr_queue_done(QMGR_QUEUE *queue) queue->busy_refcount + queue->todo_refcount); if (queue->todo.next || queue->busy.next) msg_panic("%s: queue not empty: %s", myname, queue->name); - if (queue->window <= 0) - msg_panic("%s: window %d", myname, queue->window); + if (!QMGR_QUEUE_READY(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); if (queue->dsn) msg_panic("%s: queue %s: spurious reason %s", myname, queue->name, queue->dsn->reason); diff --git a/postfix/src/qmgr/qmgr_transport.c b/postfix/src/qmgr/qmgr_transport.c index e702bac52..1265c6f0c 100644 --- a/postfix/src/qmgr/qmgr_transport.c +++ b/postfix/src/qmgr/qmgr_transport.c @@ -389,7 +389,12 @@ QMGR_TRANSPORT *qmgr_transport_create(const char *name) transport->init_dest_concurrency = get_mail_conf_int2(name, _INIT_DEST_CON, var_init_dest_concurrency, 1, 0); + transport->rate_delay = get_mail_conf_time2(name, _DEST_RATE_DELAY, + var_dest_rate_delay, + 's', 0, 0); + if (transport->rate_delay > 0) + transport->dest_concurrency_limit = 1; if (transport->dest_concurrency_limit != 0 && transport->dest_concurrency_limit < transport->init_dest_concurrency) transport->init_dest_concurrency = transport->dest_concurrency_limit;