From: Timo Sirainen Date: Thu, 16 Oct 2025 09:27:52 +0000 (+0300) Subject: master: accept() unix/inet connections before creating child process to handle it X-Git-Tag: 2.4.2~32 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4e75476a953d8e8dee4c45fca037037bd0be3bbd;p=thirdparty%2Fdovecot%2Fcore.git master: accept() unix/inet connections before creating child process to handle it This way if the child processes are slow to start, it won't affect how rapidly they can be created. --- diff --git a/src/master/main.c b/src/master/main.c index 60d2a695b2..9fbec89239 100644 --- a/src/master/main.c +++ b/src/master/main.c @@ -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"); diff --git a/src/master/service-anvil.c b/src/master/service-anvil.c index 1c2adc5641..9a87405bbe 100644 --- a/src/master/service-anvil.c +++ b/src/master/service-anvil.c @@ -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); } } diff --git a/src/master/service-monitor.c b/src/master/service-monitor.c index e39a6498a7..ffe7a6eccd 100644 --- a/src/master/service-monitor.c +++ b/src/master/service-monitor.c @@ -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); diff --git a/src/master/service-process.c b/src/master/service-process.c index 02d735e5c2..f9c4a27265 100644 --- a/src/master/service-process.c +++ b/src/master/service-process.c @@ -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); diff --git a/src/master/service-process.h b/src/master/service-process.h index 3a22df973b..bbd354b1fa 100644 --- a/src/master/service-process.h +++ b/src/master/service-process.h @@ -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); diff --git a/src/master/service.c b/src/master/service.c index 1e13298880..85ea57a678 100644 --- a/src/master/service.c +++ b/src/master/service.c @@ -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) { diff --git a/src/master/service.h b/src/master/service.h index f22f0619ae..ffdaa69419 100644 --- a/src/master/service.h +++ b/src/master/service.h @@ -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;