This feature is available in Postfix 2.4 and later.
+%PARAM default_transport_rate_delay 0s + +The default amount of delay that is inserted between individual +deliveries over the same message delivery transport, regardless of +destination. If non-zero, all deliveries over the same message +delivery transport will happen one at a time.
+ +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.
+ +Use transport_transport_rate_delay to specify a +transport-specific override, where the initial transport is +the master.cf name of the message delivery transport.
+ +This feature is available in Postfix 3.1 and later.
+ +%PARAM transport_transport_rate_delay $default_transport_rate_delay + +A transport-specific override for the default_transport_rate_delay +parameter value, where the initial transport in the parameter +name is the master.cf name of the message delivery transport.
+ %PARAM default_destination_rate_delay 0sThe default amount of delay that is inserted between individual diff --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL --exclude=.indent.pro --exclude=Makefile.in -r -ur /var/tmp/postfix-3.1-20150617-nonprod/src/global/mail_params.h ./src/global/mail_params.h --- /var/tmp/postfix-3.1-20150617-nonprod/src/global/mail_params.h 2015-06-14 19:41:14.000000000 -0400 +++ ./src/global/mail_params.h 2015-06-21 11:48:34.000000000 -0400 @@ -3396,6 +3396,11 @@ #define DEF_DEST_RATE_DELAY "0s" extern int var_dest_rate_delay; +#define VAR_XPORT_RATE_DELAY "default_transport_rate_delay" +#define _XPORT_RATE_DELAY "_transport_rate_delay" +#define DEF_XPORT_RATE_DELAY "0s" +extern int var_xport_rate_delay; + /* * Stress handling. */ diff --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL --exclude=.indent.pro --exclude=Makefile.in -r -ur /var/tmp/postfix-3.1-20150617-nonprod/src/postconf/postconf_service.c ./src/postconf/postconf_service.c --- /var/tmp/postfix-3.1-20150617-nonprod/src/postconf/postconf_service.c 2014-12-06 20:35:32.000000000 -0500 +++ ./src/postconf/postconf_service.c 2015-06-21 14:40:41.000000000 -0400 @@ -134,6 +134,7 @@ _CONC_NEG_FDBACK, VAR_CONC_NEG_FDBACK, _CONC_COHORT_LIM, VAR_CONC_COHORT_LIM, _DEST_RATE_DELAY, VAR_DEST_RATE_DELAY, + _XPORT_RATE_DELAY, VAR_XPORT_RATE_DELAY, 0, }; static const PCF_STRING_NV spawn_params[] = { diff --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL --exclude=.indent.pro --exclude=Makefile.in -r -ur /var/tmp/postfix-3.1-20150617-nonprod/src/qmgr/qmgr.c ./src/qmgr/qmgr.c --- /var/tmp/postfix-3.1-20150617-nonprod/src/qmgr/qmgr.c 2015-01-29 17:15:30.000000000 -0500 +++ ./src/qmgr/qmgr.c 2015-06-21 12:18:34.888155000 -0400 @@ -301,6 +301,14 @@ /* on the value of the corresponding per-destination recipient limit. /* .IP "\fItransport\fB_destination_rate_delay $default_destination_rate_delay /* Idem, for delivery via the named message \fItransport\fR. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBdefault_transport_rate_delay (0s)\fR" +/* The default amount of delay that is inserted between individual +/* deliveries over the same message delivery transport, regardless of +/* destination. +/* .IP "\fItransport\fB_transport_rate_delay $default_transport_rate_delay +/* Idem, for delivery via the named message \fItransport\fR. /* SAFETY CONTROLS /* .ad /* .fi @@ -446,6 +454,7 @@ char *var_conc_neg_feedback; int var_conc_cohort_limit; int var_conc_feedback_debug; +int var_xport_rate_delay; int var_dest_rate_delay; char *var_def_filter_nexthop; int var_qmgr_daemon_timeout; @@ -702,6 +711,7 @@ 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_XPORT_RATE_DELAY, DEF_XPORT_RATE_DELAY, &var_xport_rate_delay, 0, 0, VAR_DEST_RATE_DELAY, DEF_DEST_RATE_DELAY, &var_dest_rate_delay, 0, 0, VAR_QMGR_DAEMON_TIMEOUT, DEF_QMGR_DAEMON_TIMEOUT, &var_qmgr_daemon_timeout, 1, 0, VAR_QMGR_IPC_TIMEOUT, DEF_QMGR_IPC_TIMEOUT, &var_qmgr_ipc_timeout, 1, 0, diff --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL --exclude=.indent.pro --exclude=Makefile.in -r -ur /var/tmp/postfix-3.1-20150617-nonprod/src/qmgr/qmgr.h ./src/qmgr/qmgr.h --- /var/tmp/postfix-3.1-20150617-nonprod/src/qmgr/qmgr.h 2014-07-07 16:15:53.000000000 -0400 +++ ./src/qmgr/qmgr.h 2015-06-21 15:50:27.000000000 -0400 @@ -204,10 +204,12 @@ QMGR_FEEDBACK pos_feedback; /* positive feedback control */ QMGR_FEEDBACK neg_feedback; /* negative feedback control */ int fail_cohort_limit; /* flow shutdown control */ + int xport_rate_delay; /* suspend per delivery */ int rate_delay; /* suspend per delivery */ }; #define QMGR_TRANSPORT_STAT_DEAD (1<<1) +#define QMGR_TRANSPORT_STAT_RATE_LOCK (1<<2) typedef void (*QMGR_TRANSPORT_ALLOC_NOTIFY) (QMGR_TRANSPORT *, VSTREAM *); extern QMGR_TRANSPORT *qmgr_transport_select(void); diff --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL --exclude=.indent.pro --exclude=Makefile.in -r -ur /var/tmp/postfix-3.1-20150617-nonprod/src/qmgr/qmgr_deliver.c ./src/qmgr/qmgr_deliver.c --- /var/tmp/postfix-3.1-20150617-nonprod/src/qmgr/qmgr_deliver.c 2014-12-14 13:22:05.000000000 -0500 +++ ./src/qmgr/qmgr_deliver.c 2015-06-21 13:05:17.000000000 -0400 @@ -346,9 +346,10 @@ * No problems detected. Mark the transport and queue as alive. The queue * itself won't go away before we dispose of the current queue entry. */ - if (status != DELIVER_STAT_CRASH && VSTRING_LEN(dsb->reason) == 0) { + if (status != DELIVER_STAT_CRASH) { qmgr_transport_unthrottle(transport); - qmgr_queue_unthrottle(queue); + if (VSTRING_LEN(dsb->reason) == 0) + qmgr_queue_unthrottle(queue); } /* diff --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL --exclude=.indent.pro --exclude=Makefile.in -r -ur /var/tmp/postfix-3.1-20150617-nonprod/src/qmgr/qmgr_transport.c ./src/qmgr/qmgr_transport.c --- /var/tmp/postfix-3.1-20150617-nonprod/src/qmgr/qmgr_transport.c 2014-12-25 11:47:17.000000000 -0500 +++ ./src/qmgr/qmgr_transport.c 2015-06-21 14:35:20.000000000 -0400 @@ -180,7 +180,7 @@ * This routine runs after expiration of the timer set by * qmgr_transport_throttle(), or whenever a delivery transport has been * used without malfunction. In either case, we enable delivery again if - * the transport was blocked, otherwise the request is ignored. + * the transport was throttled. We always reset the transport rate lock. */ if ((transport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0) { if (msg_verbose) @@ -194,6 +194,8 @@ event_cancel_timer(qmgr_transport_unthrottle_wrapper, (void *) transport); } + if (transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) + transport->flags &= ~QMGR_TRANSPORT_STAT_RATE_LOCK; } /* qmgr_transport_throttle - disable delivery process allocation */ @@ -230,6 +232,16 @@ msg_fatal("timeout connecting to transport: %s", alloc->transport->name); } +/* qmgr_transport_rate_event - delivery process availability notice */ + +static void qmgr_transport_rate_event(int unused_event, void *context) +{ + QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context; + + alloc->notify(alloc->transport, alloc->stream); + myfree((void *) alloc); +} + /* qmgr_transport_event - delivery process availability notice */ static void qmgr_transport_event(int unused_event, void *context) @@ -261,8 +273,16 @@ /* * Notify the requestor. */ - alloc->notify(alloc->transport, alloc->stream); - myfree((void *) alloc); + if (alloc->transport->xport_rate_delay > 0) { + if ((alloc->transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) == 0) + msg_panic("transport_event: missing rate lock for transport %s", + alloc->transport->name); + event_request_timer(qmgr_transport_rate_event, (void *) alloc, + alloc->transport->xport_rate_delay); + } else { + alloc->notify(alloc->transport, alloc->stream); + myfree((void *) alloc); + } } /* qmgr_transport_select - select transport for allocation */ @@ -287,6 +307,7 @@ for (xport = qmgr_transport_list.next; xport; xport = xport->peers.next) { if ((xport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0 + || (xport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) != 0 || xport->pending >= QMGR_TRANSPORT_MAX_PEND) continue; need = xport->pending + 1; @@ -316,10 +337,19 @@ */ if (transport->flags & QMGR_TRANSPORT_STAT_DEAD) msg_panic("qmgr_transport: dead transport: %s", transport->name); + if (transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) + msg_panic("qmgr_transport: rate-locked transport: %s", transport->name); if (transport->pending >= QMGR_TRANSPORT_MAX_PEND) msg_panic("qmgr_transport: excess allocation: %s", transport->name); /* + * When this message delivery transport is rate-limited, do not select it + * again before the end of a message delivery transaction. + */ + if (transport->xport_rate_delay > 0) + transport->flags |= QMGR_TRANSPORT_STAT_RATE_LOCK; + + /* * Connect to the well-known port for this delivery service, and wake up * when a process announces its availability. Allow only a limited number * of delivery process allocation attempts for this transport. In case of @@ -392,6 +422,9 @@ transport->init_dest_concurrency = get_mail_conf_int2(name, _INIT_DEST_CON, var_init_dest_concurrency, 1, 0); + transport->xport_rate_delay = get_mail_conf_time2(name, _XPORT_RATE_DELAY, + var_xport_rate_delay, + 's', 0, 0); transport->rate_delay = get_mail_conf_time2(name, _DEST_RATE_DELAY, var_dest_rate_delay, 's', 0, 0);