]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
master: accept() unix/inet connections before creating child process to handle it
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Thu, 16 Oct 2025 09:27:52 +0000 (12:27 +0300)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Tue, 21 Oct 2025 11:03:24 +0000 (11:03 +0000)
This way if the child processes are slow to start, it won't affect how
rapidly they can be created.

src/master/main.c
src/master/service-anvil.c
src/master/service-monitor.c
src/master/service-process.c
src/master/service-process.h
src/master/service.c
src/master/service.h

index 60d2a695b2abfc7c5cc7b1c1faa726a5a11e641e..9fbec8923991397a73993aad5062f4353cee0faf 100644 (file)
@@ -404,7 +404,7 @@ sig_settings_reload(const siginfo_t *si ATTR_UNUSED,
 
        if (services->config->process_avail == 0) {
                /* we can't reload config if there's no config process. */
-               if (service_process_create(services->config) == NULL) {
+               if (service_process_create(services->config, -1, NULL) == NULL) {
                        i_error("Can't reload configuration because "
                                "we couldn't create a config process");
                        i_sd_notify(0, "READY=1");
index 1c2adc564127bc466ce771ae5562018a89ac6a3c..9a87405bbe34b49570e278ddf0ec40b0681f016c 100644 (file)
@@ -97,7 +97,7 @@ void service_anvil_monitor_start(struct service_list *service_list)
                service_list_anvil_discard_input(service_anvil_global);
        else {
                service = service_lookup_type(service_list, SERVICE_TYPE_ANVIL);
-               (void)service_process_create(service);
+               (void)service_process_create(service, -1, NULL);
        }
 }
 
index e39a6498a785a96bcd3f469f73d755d355ddab13..ffe7a6eccd8fc85ef0db940fb6a8b9a9c3a013cd 100644 (file)
@@ -383,6 +383,7 @@ static void service_drop_connections(struct service_listener *l)
 static void service_accept(struct service_listener *l)
 {
        struct service *service = l->service;
+       int fd = -1;
 
        i_assert(service->process_avail == 0);
 
@@ -393,11 +394,31 @@ static void service_accept(struct service_listener *l)
                return;
        }
 
-       /* create a child process and let it accept() this connection */
-       if (service_process_create(service) == NULL)
+       if (service->client_limit == 1 &&
+           (l->type == SERVICE_LISTENER_INET ||
+            (l->type == SERVICE_LISTENER_UNIX &&
+             !l->set.fileset.pid_listener))) {
+               /* pre-accept() a client fd for services with client_limit=1,
+                  so we can rapidly create new processes as needed. */
+               fd = net_accept(l->fd, NULL, NULL);
+               if (fd == -1) {
+                       if (!NET_ACCEPT_ENOCONN(errno))
+                               e_error(service->event, "net_accept() failed: %m");
+                       return;
+               }
+               fd_close_on_exec(fd, TRUE);
+       } else {
+               /* the created child process will accept() the connection */
+       }
+
+       if (service_process_create(service, fd, l) == NULL) {
+               /* failed to create the process */
                service_monitor_throttle(service);
-       else
+       } else if (fd == -1) {
                service_monitor_listen_stop(service);
+       }
+       if (fd != -1)
+               net_disconnect(fd);
 }
 
 static bool
@@ -414,7 +435,7 @@ service_monitor_start_count(struct service *service, unsigned int limit)
                count = limit;
 
        for (i = 0; i < count; i++) {
-               if (service_process_create(service) == NULL) {
+               if (service_process_create(service, -1, NULL) == NULL) {
                        service_monitor_throttle(service);
                        break;
                }
@@ -597,7 +618,7 @@ void services_monitor_start(struct service_list *service_list)
                service_monitor_start_extra_avail(service);
 
        if (service_list->log->status_fd[0] != -1) {
-               if (service_process_create(service_list->log) != NULL)
+               if (service_process_create(service_list->log, -1, NULL) != NULL)
                        service_monitor_listen_stop(service_list->log);
        }
 
@@ -605,7 +626,7 @@ void services_monitor_start(struct service_list *service_list)
        array_foreach_elem(&service_list->services, service) {
                if (service->type == SERVICE_TYPE_STARTUP &&
                    service->status_fd[0] != -1) {
-                       if (service_process_create(service) != NULL)
+                       if (service_process_create(service, -1, NULL) != NULL)
                                service_monitor_listen_stop(service);
                }
        }
@@ -886,7 +907,7 @@ void services_monitor_reap_children(void)
                        } else if (service == service->list->log &&
                                   service->process_count == 0) {
                                /* log service must always be running */
-                               if (service_process_create(service) == NULL)
+                               if (service_process_create(service, -1, NULL) == NULL)
                                        service_monitor_throttle(service);
                        } else {
                                service_monitor_listen_start(service);
index 02d735e5c295e102d1607407f61ff2b65ab6dcdc..f9c4a272651add3522b5bf60e2995aae8edbb383 100644 (file)
@@ -69,7 +69,9 @@ service_unix_pid_listener_get_path(struct service_listener *l, pid_t pid,
 }
 
 static void
-service_dup_fds(struct service *service)
+service_dup_fds(struct service *service, int accepted_fd,
+               const struct service_listener *accepted_listener,
+               int *accepted_listener_fd_r)
 {
        struct service_listener *const *listeners;
        ARRAY_TYPE(dup2) dups;
@@ -77,6 +79,8 @@ service_dup_fds(struct service *service)
        int fd = MASTER_LISTEN_FD_FIRST;
        unsigned int i, count, socket_listener_count;
 
+       *accepted_listener_fd_r = -1;
+
        /* stdin/stdout is already redirected to /dev/null. Other master fds
           should have been opened with fd_close_on_exec() so we don't have to
           worry about them.
@@ -88,6 +92,12 @@ service_dup_fds(struct service *service)
        listeners = array_get(&service->listeners, &count);
        t_array_init(&dups, count + 10);
 
+       if (accepted_fd != -1) {
+               /* we have a pre-accepted connection */
+               dup2_append(&dups, accepted_fd,
+                           MASTER_ACCEPTED_CLIENT_FD);
+       }
+
        switch (service->type) {
        case SERVICE_TYPE_LOG:
                i_assert(fd == MASTER_LISTEN_FD_FIRST);
@@ -137,6 +147,8 @@ service_dup_fds(struct service *service)
                                }
                        }
 
+                       if (listeners[i] == accepted_listener)
+                               *accepted_listener_fd_r = fd;
                        dup2_append(&dups, listeners[i]->fd, fd++);
 
                        env_put(t_strdup_printf("SOCKET%d_SETTINGS",
@@ -360,7 +372,9 @@ static void service_process_status_timeout(struct service_process *process)
        timeout_remove(&process->to_status);
 }
 
-struct service_process *service_process_create(struct service *service)
+struct service_process *
+service_process_create(struct service *service, int accepted_fd,
+                      const struct service_listener *accepted_listener)
 {
        static unsigned int uid_counter = 0;
        struct service_process *process;
@@ -411,9 +425,16 @@ struct service_process *service_process_create(struct service *service)
        }
        if (pid == 0) {
                /* child */
+               int accepted_listener_fd;
                service_process_setup_environment(service, uid, hostdomain);
                service_reopen_inet_listeners(service);
-               service_dup_fds(service);
+               service_dup_fds(service, accepted_fd, accepted_listener,
+                               &accepted_listener_fd);
+               if (accepted_fd != -1) {
+                       i_assert(accepted_listener_fd > 0);
+                       env_put(DOVECOT_ACCEPTED_CLIENT_LISTENER_FD_ENV,
+                               dec2str(accepted_listener_fd));
+               }
                drop_privileges(service);
                process_exec(service->executable);
        }
@@ -431,14 +452,19 @@ struct service_process *service_process_create(struct service *service)
                                    service_process_status_timeout, process);
        }
 
-       process->available_count = service->client_limit;
-       process->idle_start = ioloop_time;
        service->process_count_total++;
        service->process_count++;
-       service->process_avail++;
-       service->process_idling++;
-       DLLIST2_APPEND(&service->idle_processes_head,
-                      &service->idle_processes_tail, process);
+       if (accepted_fd == -1) {
+               process->available_count = service->client_limit;
+               process->idle_start = ioloop_time;
+               service->process_avail++;
+               service->process_idling++;
+               DLLIST2_APPEND(&service->idle_processes_head,
+                              &service->idle_processes_tail, process);
+       } else {
+               i_assert(service->client_limit == 1);
+               DLLIST_PREPEND(&service->busy_processes, process);
+       }
 
        service_list_ref(service->list);
        hash_table_insert(service_pids, POINTER_CAST(process->pid), process);
index 3a22df973b401f80df001d19315eeb6f34c12dd7..bbd354b1fa69decbae770e6b807b772a1cf360ab 100644 (file)
@@ -46,7 +46,9 @@ struct service_process {
 #define SERVICE_PROCESS_IS_INITIALIZED(process) \
        ((process)->to_status == NULL)
 
-struct service_process *service_process_create(struct service *service);
+struct service_process *
+service_process_create(struct service *service, int accepted_fd,
+                      const struct service_listener *accepted_listener);
 void service_process_destroy(struct service_process *process);
 
 void service_process_ref(struct service_process *process);
index 1e132988809e7443d74dd22d57fe960ee79cb0b3..85ea57a6785b75d64b2c93b7513cad0bf1a09c2e 100644 (file)
@@ -316,8 +316,10 @@ service_create_real(pool_t pool, struct event *event,
 
                if (strstr(unix_listeners[i]->path, "%{pid}") == NULL)
                        array_push_back(&service->listeners, &l);
-               else
+               else {
+                       l->set.fileset.pid_listener = TRUE;
                        array_push_back(&service->unix_pid_listeners, &l);
+               }
        }
        for (i = 0; i < fifo_count; i++) {
                if (fifo_listeners[i]->mode == 0) {
index f22f0619ae58dbd0f8de9bd1072e1548cd3fc5bb..ffdaa69419237a2f0acdc0ac5afb6a775dd11f92 100644 (file)
@@ -32,6 +32,7 @@ struct service_listener {
                        const struct file_listener_settings *set;
                        uid_t uid;
                        gid_t gid;
+                       bool pid_listener;
                } fileset;
                struct {
                        const struct inet_listener_settings *set;