]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-2.5-20071202
authorWietse Venema <wietse@porcupine.org>
Fri, 30 Nov 2007 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <viktor@dukhovni.org>
Tue, 5 Feb 2013 06:33:31 +0000 (06:33 +0000)
29 files changed:
postfix/HISTORY
postfix/README_FILES/SCHEDULER_README
postfix/RELEASE_NOTES
postfix/html/SCHEDULER_README.html
postfix/html/oqmgr.8.html
postfix/html/postconf.5.html
postfix/html/qmgr.8.html
postfix/man/man5/postconf.5
postfix/man/man8/oqmgr.8
postfix/man/man8/qmgr.8
postfix/mantools/postlink
postfix/proto/SCHEDULER_README.html
postfix/proto/postconf.proto
postfix/src/global/mail_params.h
postfix/src/oqmgr/qmgr.c
postfix/src/oqmgr/qmgr.h
postfix/src/oqmgr/qmgr_deliver.c
postfix/src/oqmgr/qmgr_enable.c
postfix/src/oqmgr/qmgr_entry.c
postfix/src/oqmgr/qmgr_queue.c
postfix/src/oqmgr/qmgr_transport.c
postfix/src/postconf/auto.awk
postfix/src/qmgr/qmgr.c
postfix/src/qmgr/qmgr.h
postfix/src/qmgr/qmgr_deliver.c
postfix/src/qmgr/qmgr_enable.c
postfix/src/qmgr/qmgr_entry.c
postfix/src/qmgr/qmgr_queue.c
postfix/src/qmgr/qmgr_transport.c

index 6bbc5c766be45cdbc3e18520f3da4556b60d4408..50aee39bd6f822f6f51322323804b9c2a7dd67f6 100644 (file)
@@ -13863,5 +13863,18 @@ Apologies for any names omitted.
        <transport>_destination_concurrency_positive_feedback,
        <transport>_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.
index a335765bcda286500ab76b7b5cf38c91ca674a26..cba526863642cfa74170d533ecf0f51134aa7d65 100644 (file)
@@ -351,14 +351,14 @@ next section.
 
 L\bLi\bim\bmi\bit\bta\bat\bti\bio\bon\bns\bs o\bof\bf l\ble\bes\bss\bs-\b-t\bth\bha\ban\bn-\b-1\b1 p\bpe\ber\br d\bde\bel\bli\biv\bve\ber\bry\by f\bfe\bee\bed\bdb\bba\bac\bck\bk
 
-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 e\ben\bnd\bd of a sequence of events
-of length 1/feedback, while negative feedback will decrement concurrency by 1
-at the b\bbe\beg\bgi\bin\bnn\bni\bin\bng\bg 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 e\ben\bnd\bd of a sequence of events of length 1/
+feedback, while negative feedback will decrement concurrency by 1 at the
+b\bbe\beg\bgi\bin\bnn\bni\bin\bng\bg 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-
index 80dd1e5f777ff8c3d553d61e16aef02f8268ae74..60752c5c443392ebcddb48d2483cdb4a83db2862 100644 (file)
@@ -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
 ============================================
 
index 015a6d0f0f4a5fb32d67e08967dadd6311477c46..cc8cea13e6120bf9348793a5fe8eb016ca71ea17 100644 (file)
@@ -554,7 +554,7 @@ the next section.  </p>
 
 <h3> <a name="concurrency_limitations"> Limitations of less-than-1 per delivery feedback </a> </h3>
 
-<p> The delivery concurrency scheduler with less-than-1 concurrency
+<p> 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. </p>
 
 <p> 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
index ff0d148423dc01eec8849e5fc7a1d90498c071ca..8e62581434b305f6d1e2457780e15acbd033e76f 100644 (file)
@@ -242,18 +242,18 @@ OQMGR(8)                                                              OQMGR(8)
               Idem, for delivery via the named message <i>transport</i>.
 
        <b><a href="postconf.5.html#default_destination_concurrency_negative_feedback">default_destination_concurrency_negative_feedback</a> (1)</b>
-              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.
 
        <b><a href="postconf.5.html#transport_destination_concurrency_positive_feedback"><i>transport</i>_destination_concurrency_negative_feedback</a></b>
        <b>($<a href="postconf.5.html#default_destination_concurrency_negative_feedback">default_destination_concurrency_negative_feedback</a>)</b>
               Idem, for delivery via the named message <i>transport</i>.
 
        <b><a href="postconf.5.html#default_destination_concurrency_positive_feedback">default_destination_concurrency_positive_feedback</a> (1)</b>
-              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.
 
        <b><a href="postconf.5.html#transport_destination_concurrency_positive_feedback"><i>transport</i>_destination_concurrency_positive_feedback</a></b>
        <b>($<a href="postconf.5.html#default_destination_concurrency_positive_feedback">default_destination_concurrency_positive_feedback</a>)</b>
@@ -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:
+
+       <b><a href="postconf.5.html#default_delivery_rate_delay">default_delivery_rate_delay</a> (0s)</b>
+              The  default  amount  of  delay  that  is  inserted
+              between individual deliveries to the same  destina-
+              tion;  with  per-destination recipient limit &gt; 1, a
+              destination is a domain, otherwise it is a  recipi-
+              ent.
+
+       <b><a href="postconf.5.html#transport_delivery_rate_delay"><i>transport</i>_delivery_rate_delay</a> $<a href="postconf.5.html#default_delivery_rate_delay">default_delivery_rate_delay</a></b>
+              Idem, for delivery via the named message <i>transport</i>.
+
 <b>MISCELLANEOUS CONTROLS</b>
        <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
-              The  default  location  of  the Postfix <a href="postconf.5.html">main.cf</a> and
+              The default location of  the  Postfix  <a href="postconf.5.html">main.cf</a>  and
               <a href="master.5.html">master.cf</a> configuration files.
 
        <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
-              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.
 
        <b><a href="postconf.5.html#defer_transports">defer_transports</a> (empty)</b>
@@ -317,11 +329,11 @@ OQMGR(8)                                                              OQMGR(8)
               "<b>sendmail -q</b>" or equivalent.
 
        <b><a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> (2)</b>
-              The maximal number  of  digits  after  the  decimal
+              The  maximal  number  of  digits  after the decimal
               point when logging sub-second delay values.
 
        <b><a href="postconf.5.html#helpful_warnings">helpful_warnings</a> (yes)</b>
-              Log  warnings  about problematic configuration set-
+              Log warnings about problematic  configuration  set-
               tings, and provide helpful suggestions.
 
        <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
@@ -329,23 +341,23 @@ OQMGR(8)                                                              OQMGR(8)
               over an internal communication channel.
 
        <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
-              The  process  ID  of  a  Postfix  command or daemon
+              The process ID  of  a  Postfix  command  or  daemon
               process.
 
        <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
-              The process name of a  Postfix  command  or  daemon
+              The  process  name  of  a Postfix command or daemon
               process.
 
        <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
-              The  location of the Postfix top-level queue direc-
+              The location of the Postfix top-level queue  direc-
               tory.
 
        <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
               The syslog facility of Postfix logging.
 
        <b><a href="postconf.5.html#syslog_name">syslog_name</a> (postfix)</b>
-              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".
 
 <b>FILES</b>
@@ -368,7 +380,7 @@ OQMGR(8)                                                              OQMGR(8)
        <a href="QSHAPE_README.html">QSHAPE_README</a>, Postfix queue analysis
 
 <b>LICENSE</b>
-       The  Secure  Mailer  license must be distributed with this
+       The Secure Mailer license must be  distributed  with  this
        software.
 
 <b>AUTHOR(S)</b>
index 94ed21349136c1d866b4f4035eed08eb231765fe..b81212e4cb088b3e6f59b708d6b013a87f1a94e1 100644 (file)
@@ -1584,6 +1584,35 @@ Examples:
 </pre>
 
 
+</DD>
+
+<DT><b><a name="default_delivery_rate_delay">default_delivery_rate_delay</a>
+(default: 0s)</b></DT><DD>
+
+<p> The default amount of delay that is inserted between individual
+deliveries to the same destination; with per-destination recipient
+limit &gt; 1, a destination is a domain, otherwise it is a recipient.
+</p>
+
+<p> To enable the delay, specify a non-zero time value (an integral
+value plus an optional one-letter suffix that specifies the time
+unit). </p>
+
+<p> Time units: s (seconds), m (minutes), h (hours), d (days), w
+(weeks). The default time unit is s (seconds). </p>
+
+<p> NOTE: the delay is enforced by the queue manager. The delay
+timer state does not survive "postfix reload" or "postfix stop".
+</p>
+
+<p> Use <a href="postconf.5.html#transport_delivery_rate_delay"><i>transport</i>_delivery_rate_delay</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
 </DD>
 
 <DT><b><a name="default_delivery_slot_cost">default_delivery_slot_cost</a>
@@ -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.
 </p>
 
+<p> Use <a href="postconf.5.html#transport_delivery_slot_cost"><i>transport</i>_delivery_slot_cost</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
 <p>
 Examples:
 </p>
@@ -1653,6 +1687,11 @@ Note that the full amount will still have to be accumulated before
 another preemption can take place later.
 </p>
 
+<p> Use <a href="postconf.5.html#transport_delivery_slot_discount"><a href="postconf.5.html#transport_delivery_slot_discount"><i>transport</i>_delivery_slot_discount</a></a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
 
 </DD>
 
@@ -1674,6 +1713,11 @@ Note that the full amount will still have to be accumulated before
 another preemption can take place later.
 </p>
 
+<p> Use <a href="postconf.5.html#transport_delivery_slot_loan"><i>transport</i>_delivery_slot_loan</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
 
 </DD>
 
@@ -1707,6 +1751,13 @@ is compatible with earlier Postfix versions. </p>
 The default maximal number of parallel deliveries to the same
 destination.  This is the default limit for delivery via the <a href="lmtp.8.html">lmtp(8)</a>,
 <a href="pipe.8.html">pipe(8)</a>, <a href="smtp.8.html">smtp(8)</a> and <a href="virtual.8.html">virtual(8)</a> delivery agents.
+With per-destination recipient limit &gt; 1, a destination is a domain,
+otherwise it is a recipient.
+</p>
+
+<p> Use <a href="postconf.5.html#transport_destination_concurrency_limit"><i>transport</i>_destination_concurrency_limit</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
 </p>
 
 
@@ -1840,6 +1891,11 @@ This is the default limit for delivery via the <a href="lmtp.8.html">lmtp(8)</a>
 the corresponding per-destination concurrency limit from concurrency
 per domain into concurrency per recipient.  </p>
 
+<p> Use <a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
 
 </DD>
 
@@ -1855,6 +1911,11 @@ recipients slots for the chosen message in order to avoid performance
 degradation.
 </p>
 
+<p> Use <a href="postconf.5.html#transport_extra_recipient_limit"><i>transport</i>_extra_recipient_limit</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
 
 </DD>
 
@@ -1868,6 +1929,11 @@ which would never accumulate at least this many delivery slots
 (subject to slot cost parameter as well) are never preempted.
 </p>
 
+<p> Use <a href="postconf.5.html#transport_minimum_delivery_slots"><i>transport</i>_minimum_delivery_slots</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
 
 </DD>
 
@@ -2034,6 +2100,11 @@ to the respective transports.  See also <a href="postconf.5.html#default_extra_r
 and <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a>.
 </p>
 
+<p> Use <a href="postconf.5.html#transport_recipient_limit"><i>transport</i>_recipient_limit</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
 
 </DD>
 
@@ -2048,6 +2119,11 @@ make sure the recipients are refilled in timely manner even when
 $<a href="postconf.5.html#default_recipient_refill_limit">default_recipient_refill_limit</a> is too high for too slow deliveries.
 </p>
 
+<p> Use <a href="postconf.5.html#transport_recipient_refill_delay"><i>transport</i>_recipient_refill_delay</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
 <p> This feature is available in Postfix 2.4 and later. </p>
 
 
@@ -2064,6 +2140,11 @@ $<a href="postconf.5.html#default_recipient_refill_delay">default_recipient_refi
 lower than this when this limit is too high for too slow deliveries.
 </p>
 
+<p> Use <a href="postconf.5.html#transport_recipient_refill_limit"><i>transport</i>_recipient_refill_limit</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
 <p> This feature is available in Postfix 2.4 and later. </p>
 
 
@@ -3187,6 +3268,8 @@ Examples:
 The initial per-destination concurrency level for parallel delivery
 to the same destination. This limit applies to delivery via <a href="smtp.8.html">smtp(8)</a>,
 and via the <a href="pipe.8.html">pipe(8)</a> and <a href="virtual.8.html">virtual(8)</a> delivery agents.
+With per-destination recipient limit &gt; 1, a destination is a domain,
+otherwise it is a recipient.
 </p>
 
 <p> Use <a href="postconf.5.html#transport_initial_destination_concurrency"><i>transport</i>_initial_destination_concurrency</a> to specify
@@ -12204,6 +12287,18 @@ This feature is available in Postfix 2.1 and later.
 </p>
 
 
+</DD>
+
+<DT><b><a name="transport_delivery_rate_delay">transport_delivery_rate_delay</a>
+(default: $<a href="postconf.5.html#default_delivery_rate_delay">default_delivery_rate_delay</a>)</b></DT><DD>
+
+<p> A transport-specific override for the <a href="postconf.5.html#default_recipient_refill_delay">default_recipient_refill_delay</a>
+parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of
+the message delivery transport. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
 </DD>
 
 <DT><b><a name="transport_delivery_slot_cost">transport_delivery_slot_cost</a>
index 331cbbb3bfefa7ec7c351e6a878d0bb40d6af96c..ab4b8fabda8595d90c31cc485a2db0da9c59e19c 100644 (file)
@@ -280,18 +280,18 @@ QMGR(8)                                                                QMGR(8)
               Idem, for delivery via the named message <i>transport</i>.
 
        <b><a href="postconf.5.html#default_destination_concurrency_negative_feedback">default_destination_concurrency_negative_feedback</a> (1)</b>
-              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.
 
        <b><a href="postconf.5.html#transport_destination_concurrency_positive_feedback"><i>transport</i>_destination_concurrency_negative_feedback</a></b>
        <b>($<a href="postconf.5.html#default_destination_concurrency_negative_feedback">default_destination_concurrency_negative_feedback</a>)</b>
               Idem, for delivery via the named message <i>transport</i>.
 
        <b><a href="postconf.5.html#default_destination_concurrency_positive_feedback">default_destination_concurrency_positive_feedback</a> (1)</b>
-              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.
 
        <b><a href="postconf.5.html#transport_destination_concurrency_positive_feedback"><i>transport</i>_destination_concurrency_positive_feedback</a></b>
        <b>($<a href="postconf.5.html#default_destination_concurrency_positive_feedback">default_destination_concurrency_positive_feedback</a>)</b>
@@ -332,7 +332,7 @@ QMGR(8)                                                                QMGR(8)
               The default value  for  transport-specific  _deliv-
               ery_slot_discount settings.
 
-       <b><a href="postconf.5.html#transport_delivery_slot_discount"><i>transport</i>_delivery_slot_discount</a>          ($<a href="postconf.5.html#default_delivery_slot_discount">default_deliv</a>-</b>
+       <b><a href="postconf.5.html#transport_delivery_slot_discount"><a href="postconf.5.html#transport_delivery_slot_discount"><i>transport</i>_delivery_slot_discount</a></a>          ($<a href="postconf.5.html#default_delivery_slot_discount">default_deliv</a>-</b>
        <b><a href="postconf.5.html#default_delivery_slot_discount">ery_slot_discount</a>)</b>
               Idem, for delivery via the named message <i>transport</i>.
 
@@ -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:
+
+       <b><a href="postconf.5.html#default_delivery_rate_delay">default_delivery_rate_delay</a> (0s)</b>
+              The  default  amount  of  delay  that  is  inserted
+              between individual deliveries to the same  destina-
+              tion;  with  per-destination recipient limit &gt; 1, a
+              destination is a domain, otherwise it is a  recipi-
+              ent.
+
+       <b><a href="postconf.5.html#transport_delivery_rate_delay"><i>transport</i>_delivery_rate_delay</a> $<a href="postconf.5.html#default_delivery_rate_delay">default_delivery_rate_delay</a></b>
+              Idem, for delivery via the named message <i>transport</i>.
+
 <b>MISCELLANEOUS CONTROLS</b>
        <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
-              The  default  location  of  the Postfix <a href="postconf.5.html">main.cf</a> and
+              The default location of  the  Postfix  <a href="postconf.5.html">main.cf</a>  and
               <a href="master.5.html">master.cf</a> configuration files.
 
        <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
-              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.
 
        <b><a href="postconf.5.html#defer_transports">defer_transports</a> (empty)</b>
@@ -389,11 +401,11 @@ QMGR(8)                                                                QMGR(8)
               "<b>sendmail -q</b>" or equivalent.
 
        <b><a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> (2)</b>
-              The maximal number  of  digits  after  the  decimal
+              The  maximal  number  of  digits  after the decimal
               point when logging sub-second delay values.
 
        <b><a href="postconf.5.html#helpful_warnings">helpful_warnings</a> (yes)</b>
-              Log  warnings  about problematic configuration set-
+              Log warnings about problematic  configuration  set-
               tings, and provide helpful suggestions.
 
        <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
@@ -401,23 +413,23 @@ QMGR(8)                                                                QMGR(8)
               over an internal communication channel.
 
        <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
-              The  process  ID  of  a  Postfix  command or daemon
+              The process ID  of  a  Postfix  command  or  daemon
               process.
 
        <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
-              The process name of a  Postfix  command  or  daemon
+              The  process  name  of  a Postfix command or daemon
               process.
 
        <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
-              The  location of the Postfix top-level queue direc-
+              The location of the Postfix top-level queue  direc-
               tory.
 
        <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
               The syslog facility of Postfix logging.
 
        <b><a href="postconf.5.html#syslog_name">syslog_name</a> (postfix)</b>
-              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".
 
 <b>FILES</b>
@@ -441,7 +453,7 @@ QMGR(8)                                                                QMGR(8)
        <a href="QSHAPE_README.html">QSHAPE_README</a>, Postfix queue analysis
 
 <b>LICENSE</b>
-       The  Secure  Mailer  license must be distributed with this
+       The Secure Mailer license must be  distributed  with  this
        software.
 
 <b>AUTHOR(S)</b>
index d087937492a07a7f88206a3817cb44ebfb8b68f4..0620f2267bb3e5d6bc5f841bdb5853efa5ce749e 100644 (file)
@@ -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
index eb058f7389f8b5a6025eaaf121a5105e70d7baef..cf32b36d3a2a7db2b6f09250acc0559a6ef75442 100644 (file)
@@ -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
index e3a5bde2bc7a8a416f1b52fc8b99cc6f8f905fb6..b0a5c1e638b50ab137baaa9bf99829c463f6377d 100644 (file)
@@ -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
index 2a842b7fcb26bcd3ce17770fdfcbb138a1fa66e3..f4e9b44091e58d58a4bdcec72b9cb5eff2eeb869 100755 (executable)
@@ -339,6 +339,7 @@ while (<>) {
     s;\bdefault_destination_concur[-</Bb>]*\n* *[<Bb>]*rency_positive_feedback\b;<a href="postconf.5.html#default_destination_concurrency_positive_feedback">$&</a>;g;
     s;\bdefault_destination_con[-</Bb>]*\n* *[<Bb>]*currency_failed_cohort_limit\b;<a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">$&</a>;g;
     s;\bdestination_concurrency_feedback_debug\b;<a href="postconf.5.html#destination_concurrency_feedback_debug">$&</a>;g;
+    s;\bdefault_delivery_rate_delay\b;<a href="postconf.5.html#default_delivery_rate_delay">$&</a>;g;
 
     s;\bqmqpd_error_delay\b;<a href="postconf.5.html#qmqpd_error_delay">$&</a>;g;
     s;\bqmqpd_timeout\b;<a href="postconf.5.html#qmqpd_timeout">$&</a>;g;
@@ -635,7 +636,8 @@ while (<>) {
     s;(<i>transport</i>)(<b>)?(_recipient_refill_delay)\b;$2<a href="postconf.5.html#transport_recipient_refill_delay">$1$3</a>;g;
     s;(<i>transport</i>)(<b>)?(_recipient_refill_limit)\b;$2<a href="postconf.5.html#transport_recipient_refill_limit">$1$3</a>;g;
     s;(<i>transport</i>)(<b>)?(_time_limit)\b;$2<a href="postconf.5.html#transport_time_limit">$1$3</a>;g;
-    s;(<i>transport</i>)(<b>)?(delivery_slot_discount)\b;$2<a href="postconf.5.html#transportdelivery_slot_discount">$1$3</a>;g;
+    s;(<i>transport</i>)(<b>)?(_delivery_slot_discount)\b;$2<a href="postconf.5.html#transport_delivery_slot_discount">$1$3</a>;g;
+    s;(<i>transport</i>)(<b>)?(_delivery_rate_delay)\b;$2<a href="postconf.5.html#transport_delivery_rate_delay">$1$3</a>;g;
 
     # Undo hyperlinks of manual pages with the same name as parameters.
 
index afda19e2d839a1d4d1e3a887d78bb86a23282347..ebd67c87a4c0e980c2c255cb7ebb20e98364238a 100644 (file)
@@ -554,7 +554,7 @@ the next section.  </p>
 
 <h3> <a name="concurrency_limitations"> Limitations of less-than-1 per delivery feedback </a> </h3>
 
-<p> The delivery concurrency scheduler with less-than-1 concurrency
+<p> 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. </p>
 
 <p> 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
index fc2ea058e6a0e805ee6f631aa513cc07ab724093..f5f1771eb2407730e1e0766d91e7ad53677bd31c 100644 (file)
@@ -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.
 </p>
 
+<p> Use <i>transport</i>_delivery_slot_cost to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
 <p>
 Examples:
 </p>
@@ -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 &gt; 1, a destination is a domain,
+otherwise it is a recipient.
+</p>
+
+<p> Use <i>transport</i>_destination_concurrency_limit to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
 </p>
 
 %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.  </p>
 
+<p> Use <i>transport</i>_destination_recipient_limit to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
 %PARAM default_extra_recipient_limit 1000
 
 <p>
@@ -925,6 +942,11 @@ recipients slots for the chosen message in order to avoid performance
 degradation.
 </p>
 
+<p> Use <i>transport</i>_extra_recipient_limit to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
 %PARAM default_minimum_delivery_slots 3
 
 <p>
@@ -934,6 +956,11 @@ which would never accumulate at least this many delivery slots
 (subject to slot cost parameter as well) are never preempted.
 </p>
 
+<p> Use <i>transport</i>_minimum_delivery_slots to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
 %PARAM default_privs nobody
 
 <p>
@@ -1097,6 +1124,11 @@ to the respective transports.  See also default_extra_recipient_limit
 and qmgr_message_recipient_minimum.
 </p>
 
+<p> Use <i>transport</i>_recipient_limit to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
 %PARAM default_recipient_refill_limit 100
 
 <p>
@@ -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.
 </p>
 
+<p> Use <i>transport</i>_recipient_refill_limit to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
 <p> This feature is available in Postfix 2.4 and later. </p>
 
 %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.
 </p>
 
+<p> Use <i>transport</i>_recipient_refill_delay to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
 <p> This feature is available in Postfix 2.4 and later. </p>
 
 %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 &gt; 1, a destination is a domain,
+otherwise it is a recipient.
 </p>
 
 <p> Use <i>transport</i>_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.
 </p>
 
+<p> Use <i>transport</i>_delivery_slot_discount to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
 %PARAM default_delivery_slot_loan 3
 
 <p>
@@ -6583,6 +6632,11 @@ Note that the full amount will still have to be accumulated before
 another preemption can take place later.
 </p>
 
+<p> Use <i>transport</i>_delivery_slot_loan to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
 %CLASS verp VERP Support
 
 <p>
@@ -10931,3 +10985,36 @@ parameter value, where <i>transport</i> is the master.cf name of
 the message delivery transport. </p>
 
 <p> This feature is available in Postfix 2.4 and later. </p>
+
+%PARAM default_delivery_rate_delay 0s
+
+<p> The default amount of delay that is inserted between individual
+deliveries to the same destination; with per-destination recipient
+limit &gt; 1, a destination is a domain, otherwise it is a recipient.
+</p>
+
+<p> To enable the delay, specify a non-zero time value (an integral
+value plus an optional one-letter suffix that specifies the time
+unit). </p>
+
+<p> Time units: s (seconds), m (minutes), h (hours), d (days), w
+(weeks). The default time unit is s (seconds). </p>
+
+<p> NOTE: the delay is enforced by the queue manager. The delay
+timer state does not survive "postfix reload" or "postfix stop".
+</p>
+
+<p> Use <i>transport</i>_delivery_rate_delay to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM transport_delivery_rate_delay $default_delivery_rate_delay
+
+<p> A transport-specific override for the default_recipient_refill_delay
+parameter value, where <i>transport</i> is the master.cf name of
+the message delivery transport. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
index 3f82ac8201b144d5d866d447ac0c5148e08832a6..cb46de241d0d1fb058a8c8a6007bf117d23e0e8f 100644 (file)
@@ -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
index 6d82a7fe945929e69ae3e58df772203d4a2c404a..fcc95a2f5d6af4088870caee99600262168963a8 100644 (file)
 /* .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"
 /* .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[] = {
index 1cc8bedabc99e0997ad6132a989aaf05b67e5891..837c048666036d67301bf10703cfc363d4876328 100644 (file)
@@ -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
index b616797cc3ce1571ee3424eb1d3ac725cf3fdcf6..47c75d7eb190559e316aa757949a71e7bc8e5795 100644 (file)
@@ -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);
            }
        }
index 5f0c3841b9141a3e3b19783627bbc8ef42f05255..a35e46e480b55ffe02c046a631e933efc991c203 100644 (file)
@@ -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);
 }
index fda4e5790e1f180abfcbf43c7a79073311dc0206..0f0ac1840028ddb6d069f8f562ace7ab1570fac6 100644 (file)
@@ -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);
 
     /*
index 385def4a2f7a769e3074191dbf03cc96539a1557..22bf0921cad3750f17f62a09fb9ef31f80dd1ea8 100644 (file)
 /*
 /*     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.
 /*     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);
index cedb8a8150fbb9fd5f702740838edc5f07fe7cb6..aac9dc35c611450e8fbb516c4c6cf976ba84e60f 100644 (file)
@@ -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;
index 0b7c006597dbd7baf193b92620f4dda768c7f1c2..18f60c797efffdcfb303c80dfecae3c617143ecf 100644 (file)
@@ -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
 
index 828532cb93c0c1b1dbce5136c070445d84b22443..37fcc949b5f0d02497ddddd78521d585a335aba6 100644 (file)
 /* .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"
 /* .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[] = {
index f5c14e73b6efec858221c667a859469b3b7559a6..3aa5fb5ec821197d048bc2f626349f3d65f00960 100644 (file)
@@ -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
index 6a6c5e7ec53f3ab18ea9833b7975d678fb32e6f3..668c92449603f3f94d6de4091bc049c78db4352e 100644 (file)
@@ -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);
            }
        }
index 5f0c3841b9141a3e3b19783627bbc8ef42f05255..a35e46e480b55ffe02c046a631e933efc991c203 100644 (file)
@@ -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);
 }
index 2a332f3a36d7b1c59cf88ae9d59ca647122b27f0..82228d59c6c689d5177ae5cc5c322064b19e1d26 100644 (file)
@@ -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);
 
     /*
index 110c168b7ec94de0d51d5302eeefce467df3820f..f96e853b4477230bf4d2fa84a00f10582b27569f 100644 (file)
 /*
 /*     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);
index e702bac52e4875ae6f704d75df40d386cb457435..1265c6f0ccf162d345286405d5038a01d3c3df5b 100644 (file)
@@ -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;