Avoid retry db lookups in first phase of 2-phase queue run
[exim.git] / src / src / deliver.c
index 43ac7fd41d6a3e6cd8c69244294014490e856df1..d2b2c3ab240657b52763559f2d181b43ff5f5d7c 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2023 */
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -903,6 +903,7 @@ const uschar * save_domain = deliver_domain;
 const uschar * save_local =  deliver_localpart;
 const uschar * save_host = deliver_host;
 const uschar * save_address = deliver_host_address;
+uschar * save_rn = router_name, * save_tn = transport_name;
 const int      save_port =   deliver_host_port;
 
 router_name =    addr->router ? addr->router->name : NULL;
@@ -939,7 +940,8 @@ deliver_host_address = save_address;
 deliver_host =      save_host;
 deliver_localpart = save_local;
 deliver_domain =    save_domain;
-router_name = transport_name = NULL;
+router_name = save_rn;
+transport_name = save_tn;
 }
 #endif /*DISABLE_EVENT*/
 
@@ -2460,8 +2462,7 @@ if ((pid = exim_fork(US"delivery-local")) == 0)
   and close the pipe we were writing down before exiting. */
 
   (void)close(pfd[pipe_write]);
-  search_tidyup();
-  exit(EXIT_SUCCESS);
+  exim_exit(EXIT_SUCCESS);
   }
 
 /* Back in the main process: panic if the fork did not succeed. This seems
@@ -3683,20 +3684,37 @@ while (!done)
       while (*ptr++) ;
       break;
 
-    /* Z marks the logical end of the data. It is followed by '0' if
+    /* Z0 marks the logical end of the data. It is followed by '0' if
     continue_transport was NULL at the end of transporting, otherwise '1'.
     We need to know when it becomes NULL during a delivery down a passed SMTP
     channel so that we don't try to pass anything more down it. Of course, for
-    most normal messages it will remain NULL all the time. */
+    most normal messages it will remain NULL all the time.
+
+    Z1 is a suggested message_id to handle next, used during a
+    continued-transport sequence. */
 
     case 'Z':
-      if (*ptr == '0')
+      switch (*subid)
        {
-       continue_transport = NULL;
-       continue_hostname = NULL;
+       case '0':
+         if (*ptr == '0')
+           {
+           continue_transport = NULL;
+           continue_hostname = NULL;
+           }
+         done = TRUE;
+         DEBUG(D_deliver) debug_printf("Z0%c item read\n", *ptr);
+         break;
+       case '1':
+         if (continue_hostname)
+           {
+           Ustrncpy(continue_next_id, ptr, MESSAGE_ID_LENGTH);
+           continue_sequence++;
+           }
+         DEBUG(D_deliver) debug_printf("continue_next_id: %s%s\n",
+           continue_next_id, continue_hostname ? "" : " (ignored)");
+         break;
        }
-      done = TRUE;
-      DEBUG(D_deliver) debug_printf("Z0%c item read\n", *ptr);
       break;
 
     /* Anything else is a disaster. */
@@ -4774,7 +4792,11 @@ all pipes, so I do not see a reason to use non-blocking IO here
     is flagged by an identifying byte, and is then in a fixed format (with
     strings terminated by zeros), and there is a final terminator at the
     end. The host information and retry information is all attached to
-    the first address, so that gets sent at the start. */
+    the first address, so that gets sent at the start.
+
+    Result item tags:
+      A C D H I K L P R S T X Z
+    */
 
     /* Host unusability information: for most success cases this will
     be null. */
@@ -4783,7 +4805,7 @@ all pipes, so I do not see a reason to use non-blocking IO here
       {
       if (!h->address || h->status < hstatus_unusable) continue;
       sprintf(CS big_buffer, "%c%c%s", h->status, h->why, h->address);
-      rmt_dlv_checked_write(fd, 'H', '0', big_buffer, Ustrlen(big_buffer+2) + 3);
+      rmt_dlv_checked_write(fd, 'H','0', big_buffer, Ustrlen(big_buffer+2) + 3);
       }
 
     /* The number of bytes written. This is the same for each address. Even
@@ -5016,15 +5038,19 @@ all pipes, so I do not see a reason to use non-blocking IO here
       rmt_dlv_checked_write(fd, 'I', '0', big_buffer, ptr - big_buffer);
       }
 
+    /* Continuation message-id */
+    if (*continue_next_id)
+      rmt_dlv_checked_write(fd, 'Z', '1', continue_next_id, MESSAGE_ID_LENGTH);
+
     /* Add termination flag, close the pipe, and that's it. The character
-    after 'Z' indicates whether continue_transport is now NULL or not.
+    after "Z0" indicates whether continue_transport is now NULL or not.
     A change from non-NULL to NULL indicates a problem with a continuing
     connection. */
 
     big_buffer[0] = continue_transport ? '1' : '0';
     rmt_dlv_checked_write(fd, 'Z', '0', big_buffer, 1);
     (void)close(fd);
-    exit(EXIT_SUCCESS);
+    exim_exit(EXIT_SUCCESS);
     }
 
   /* Back in the mainline: close the unwanted half of the pipe. */
@@ -5500,16 +5526,14 @@ Returns:      nothing
 */
 
 static void
-do_duplicate_check(address_item **anchor)
+do_duplicate_check(address_item ** anchor)
 {
-address_item *addr;
+address_item * addr;
 while ((addr = *anchor))
   {
-  tree_node *tnode;
+  tree_node * tnode;
   if (testflag(addr, af_pfr))
-    {
-    anchor = &(addr->next);
-    }
+    anchor = &addr->next;
   else if ((tnode = tree_search(tree_duplicates, addr->unique)))
     {
     DEBUG(D_deliver|D_route)
@@ -5522,7 +5546,7 @@ while ((addr = *anchor))
   else
     {
     tree_add_duplicate(addr->unique, addr);
-    anchor = &(addr->next);
+    anchor = &addr->next;
     }
   }
 }
@@ -6432,16 +6456,19 @@ Returns:      When the global variable mua_wrapper is FALSE:
 int
 deliver_message(const uschar * id, BOOL forced, BOOL give_up)
 {
-int i, rc;
-int final_yield = DELIVER_ATTEMPTED_NORMAL;
-time_t now = time(NULL);
-address_item *addr_last = NULL;
-uschar *filter_message = NULL;
-int process_recipients = RECIP_ACCEPT;
-open_db dbblock;
-open_db *dbm_file;
+int i, rc, final_yield, process_recipients;
+time_t now;
+address_item * addr_last;
+uschar * filter_message, * info;
+open_db dbblock, * dbm_file;
 extern int acl_where;
-uschar *info;
+CONTINUED_ID:
+
+final_yield = DELIVER_ATTEMPTED_NORMAL;
+now = time(NULL);
+addr_last = NULL;
+filter_message = NULL;
+process_recipients = RECIP_ACCEPT;
 
 #ifdef MEASURE_TIMING
 report_time_since(&timestamp_startup, US"delivery start");     /* testcase 0022, 2100 */
@@ -6516,7 +6543,7 @@ opening the data file, message_subdir gets set. */
 if ((deliver_datafile = spool_open_datafile(id)) < 0)
   return continue_closedown();  /* yields DELIVER_NOT_ATTEMPTED */
 
-/* tHe value of message_size at this point has been set to the data length,
+/* The value of message_size at this point has been set to the data length,
 plus one for the blank line that notionally precedes the data. */
 
 /* Now read the contents of the header file, which will set up the headers in
@@ -7244,9 +7271,13 @@ while (addr_new)           /* Loop until all addresses dealt with */
   address_item * addr, * parent;
 
   /* Failure to open the retry database is treated the same as if it does
-  not exist. In both cases, dbm_file is NULL. */
+  not exist. In both cases, dbm_file is NULL.  For the first stage of a 2-phase
+  queue run don't bother checking domain- or address-retry info; they will take
+  effect on the second stage. */
 
-  if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE)))
+  if (f.queue_2stage)
+    dbm_file = NULL;
+  else if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE)))
     DEBUG(D_deliver|D_retry|D_route|D_hints_lookup)
       debug_printf("no retry data available\n");
 
@@ -8625,6 +8656,17 @@ DEBUG(D_deliver) debug_printf("end delivery of %s\n", id);
 report_time_since(&timestamp_startup, US"delivery end"); /* testcase 0005 */
 #endif
 
+/* If the transport suggested another message to deliver, go round again. */
+
+if (final_yield == DELIVER_ATTEMPTED_NORMAL && *continue_next_id)
+  {
+  addr_defer = addr_failed = addr_succeed = NULL;
+  tree_duplicates = NULL;      /* discard dups info from old message */
+  id = string_copyn(continue_next_id, MESSAGE_ID_LENGTH);
+  continue_next_id[0] = '\0';
+  goto CONTINUED_ID;
+  }
+
 /* It is unlikely that there will be any cached resources, since they are
 released after routing, and in the delivery subprocesses. However, it's
 possible for an expansion for something afterwards (for example,