If a message is older than the "first failed" time when computing a
[exim.git] / src / src / retry.c
index 9c90f6699affcad795beafeaf21d87dce35da546..eb4cd46cc18900189a3b0d20fe10295615ef037f 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/retry.c,v 1.2 2005/01/04 10:00:42 ph10 Exp $ */
+/* $Cambridge: exim/src/src/retry.c,v 1.7 2006/02/09 14:50:58 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2006 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with retrying unsuccessful deliveries. */
@@ -46,8 +46,17 @@ if (retry != NULL && retry->rules != NULL)
   for (last_rule = retry->rules;
        last_rule->next != NULL;
        last_rule = last_rule->next);
+  DEBUG(D_transport|D_retry)
+    debug_printf("now=%d received_time=%d diff=%d timeout=%d\n",
+      (int)now, received_time, (int)(now - received_time),
+      last_rule->timeout);
   address_timeout = (now - received_time > last_rule->timeout);
   }
+else
+  {
+  DEBUG(D_transport|D_retry)
+    debug_printf("no retry rule found: assume timed out\n");
+  }
 
 return address_timeout;
 }
@@ -340,23 +349,38 @@ retry_config *
 retry_find_config(uschar *key, uschar *alternate, int basic_errno,
   int more_errno)
 {
-int replace;
+int replace = 0;
 uschar *use_key, *use_alternate;
 uschar *colon = Ustrchr(key, ':');
 retry_config *yield;
 
-/* If there's a colon in the key, temporarily replace it with
-a zero to terminate the string there. */
+/* If there's a colon in the key, there are two possibilities:
+
+(1) This is a key for a host, ip address, and possibly port, in the format
+
+      hostname:ip+port
+
+    In this case, we temporarily replace the colon with a zero, to terminate
+    the string after the host name.
+
+(2) This is a key for a pipe, file, or autoreply delivery, in the format
+
+      pipe-or-file-or-auto:x@y
+
+    where x@y is the original address that provoked the delivery. The pipe or
+    file or auto will start with | or / or >, whereas a host name will start
+    with a letter or a digit. In this case we want to use the original address
+    to search for a retry rule. */
 
 if (colon != NULL)
   {
-  replace = ':';
-  }
-else
-  {
-  colon = key + Ustrlen(key);
-  replace = 0;
+  if (isalnum(*key))
+    replace = ':';
+  else
+    key = Ustrrchr(key, ':') + 1;   /* Take from the last colon */
   }
+
+if (replace == 0) colon = key + Ustrlen(key);
 *colon = 0;
 
 /* Sort out the keys */
@@ -619,10 +643,12 @@ for (i = 0; i < 3; i++)
         DEBUG(D_retry)
           {
           if ((rti->flags & rf_host) != 0)
-            debug_printf("retry for %s (%s) = %s\n", rti->key,
-              addr->domain, retry->pattern);
+            debug_printf("retry for %s (%s) = %s %d %d\n", rti->key,
+              addr->domain, retry->pattern, retry->basic_errno,
+              retry->more_errno);
           else
-            debug_printf("retry for %s = %s\n", rti->key, retry->pattern);
+            debug_printf("retry for %s = %s %d %d\n", rti->key, retry->pattern,
+              retry->basic_errno, retry->more_errno);
           }
 
         /* Set up the message for the database retry record. Because DBM
@@ -658,6 +684,16 @@ for (i = 0; i < 3; i++)
         /* Compute how long this destination has been failing */
 
         failing_interval = now - retry_record->first_failed;
+        DEBUG(D_retry) debug_printf("failing_interval=%d message_age=%d\n",
+          failing_interval, message_age);
+
+        /* If the message has been on the queue longer than the recorded time
+        of failure, use the message's age instead. This can happen when some
+        messages can be delivered and others cannot; a successful delivery will
+        reset the first_failed time, and this can lead to a failing message
+        being retried too often. */
+
+        if (message_age > failing_interval) failing_interval = message_age;
 
         /* Search for the current retry rule. The cutoff time of the
         last rule is handled differently to the others. The rule continues
@@ -712,7 +748,14 @@ for (i = 0; i < 3; i++)
 
         This implements "timeout this rule if EITHER the host (or routing or
         directing) has been failing for more than the maximum time, OR if the
-        message has been on the queue for more than the maximum time." */
+        message has been on the queue for more than the maximum time."
+
+        February 2006: It is possible that this code is no longer needed
+        following the change to the retry calculation to use the message age if
+        it is larger than the time since first failure. It may be that the
+        expired flag is always set when the other conditions are met. However,
+        this is a small bit of code, and it does no harm to leave it in place,
+        just in case. */
 
         if (received_time <= retry_record->first_failed &&
             addr == endaddr && !retry_record->expired && rule != NULL)
@@ -739,15 +782,24 @@ for (i = 0; i < 3; i++)
         if (rule == NULL) next_try = now; else
           {
           if (rule->rule == 'F') next_try = now + rule->p1;
-          else  /* assume rule = 'G' */
+          else  /* rule = 'G' or 'H' */
             {
             int last_predicted_gap =
               retry_record->next_try - retry_record->last_try;
             int last_actual_gap = now - retry_record->last_try;
             int lastgap = (last_predicted_gap < last_actual_gap)?
               last_predicted_gap : last_actual_gap;
-            next_try = now + ((lastgap < rule->p1)? rule->p1 :
-               (lastgap * rule->p2)/1000);
+            int next_gap = (lastgap * rule->p2)/1000;
+            if (rule->rule == 'G')
+              {
+              next_try = now + ((lastgap < rule->p1)? rule->p1 : next_gap);
+              }
+            else  /* The 'H' rule */
+              {
+              next_try = now + rule->p1;
+              if (next_gap > rule->p1)
+                next_try += random_number(next_gap - rule->p1);
+              }
             }
           }
 
@@ -842,6 +894,9 @@ for (i = 0; i < 3; i++)
           setflag(addr, af_retry_timedout);
           addr->message = (addr->message == NULL)? US"retry timeout exceeded" :
             string_sprintf("%s: retry timeout exceeded", addr->message);
+          addr->user_message = (addr->user_message == NULL)?
+            US"retry timeout exceeded" :
+            string_sprintf("%s: retry timeout exceeded", addr->user_message);
           log_write(0, LOG_MAIN, "** %s%s%s%s: retry timeout exceeded",
             addr->address,
            (addr->parent == NULL)? US"" : US" <",