timeout in seconds. */
#define MASTER_SERVICE_IDLE_KILL_INTERVAL_ENV "IDLE_KILL_INTERVAL"
+/* getenv(MASTER_REUSE_PORT_ENV) is non-NULL if service_reuse_port=yes */
+#define MASTER_REUSE_PORT_ENV "REUSE_PORT"
+
/* getenv(MASTER_CONFIG_FILE_ENV) provides path to configuration file. */
#define MASTER_CONFIG_FILE_ENV "CONFIG_FILE"
bool call_avail_overflow:1;
bool config_path_changed_with_param:1;
bool have_admin_sockets:1;
+ bool reuse_port:1;
bool want_ssl_server:1;
bool config_path_from_master:1;
bool log_initialized:1;
} else {
service->version_string = PACKAGE_VERSION;
}
+ service->reuse_port = getenv(MASTER_REUSE_PORT_ENV) != NULL;
/* Load the SSL module if we already know it is necessary. It can also
get loaded later on-demand. */
struct timeval created;
/* This process can't handle any more connections. */
- if (!service->call_avail_overflow ||
- service->avail_overflow_callback == NULL)
+ if (service->avail_overflow_callback == NULL)
return TRUE;
+ if (!service->call_avail_overflow && !service->reuse_port) {
+ /* This process is full, but sibling processes aren't. Another
+ process will pick up this connection. Note that with
+ reuse_port=yes the connection is specifically assigned to
+ this process, so we are responsible for accepting or
+ rejecting it. */
+ return TRUE;
+ }
- /* Master has notified us that all processes are full, and
- we have the ability to kill old connections. */
+ /* Master has notified us that all processes are full (or with
+ reuse_port=yes this process is full), and we have the ability to
+ kill old connections. */
if (service->total_available_count > 1) {
/* This process can still create multiple concurrent
clients if we just kill some of the existing ones.
if (service->master_status.available_count == 0 && !master_admin_conn) {
if (master_service_full(service)) {
+ if (service->reuse_port) {
+ /* With reuse_port the master can't drop
+ the connections for us. We must do it
+ ourselves. */
+ int fd = net_accept(l->fd, NULL, NULL);
+ if (fd != -1)
+ i_close_fd(&fd);
+ return;
+ }
/* Stop the listener until a client has disconnected or
overflow callback has killed one. */
master_service_io_listeners_remove(service);
return FALSE;
}
- if (array_is_created(&service->parsed_inet_listeners)) {
- struct inet_listener_settings *l;
- bool seen_reuse_port = FALSE;
-
- array_foreach_elem(&service->parsed_inet_listeners, l) {
- if (l->port == 0)
- continue;
-
- if (l->reuse_port)
- seen_reuse_port = TRUE;
- else if (seen_reuse_port) {
- *error_r = t_strdup_printf("service(%s): "
- "All or none of the inet_listeners must have reuse_port=yes "
- "(missing for inet_listener %s)",
- service->name, l->name);
- return FALSE;
- }
- if (l->reuse_port &&
- service->process_min_avail != service->process_limit) {
- *error_r = t_strdup_printf("service(%s): "
- "process_min_avail must be equal to process_limit "
- "when using reuse_port=yes", service->name);
- return FALSE;
- }
- }
+ if (service->reuse_port &&
+ service->process_min_avail != service->process_limit) {
+ *error_r = t_strdup_printf("service(%s): "
+ "process_min_avail must be equal to process_limit when using service_reuse_port=yes",
+ service->name);
+ return FALSE;
}
#ifdef CONFIG_BINARY
return FALSE;
if (l1->set.inetset.set->port != l2->set.inetset.set->port)
return FALSE;
- return TRUE;
+ return l1->reuse_port_process_index == l2->reuse_port_process_index;
}
return FALSE;
}
service_active_process_count(service) >= service->process_limit)
service_login_notify(service, TRUE);
+ if (service->set->reuse_port &&
+ service->last_drop_warning +
+ SERVICE_DROP_WARN_INTERVAL_SECS <= ioloop_time) {
+ service->last_drop_warning = ioloop_time;
+ e_warning(service->event,
+ "client_limit (%u) reached, client connections are being dropped (pid=%s, reuse_port=yes)",
+ service->client_limit, dec2str(process->pid));
+ }
+
/* we may need to start more */
service_monitor_start_extra_avail(service);
service_monitor_listen_start(service);
timeout_remove(&service->to_drop_warning);
array_foreach_elem(&service->listeners, l) {
- if (l->io == NULL && l->fd != -1)
+ if (l->io == NULL && l->fd != -1 && !service->set->reuse_port)
l->io = io_add(l->fd, IO_READ, service_accept, l);
}
}
#include <signal.h>
#include <sys/wait.h>
-static void service_reopen_inet_listeners(struct service *service)
-{
- struct service_listener *const *listeners;
- unsigned int i, count;
- int old_fd;
-
- listeners = array_get(&service->listeners, &count);
- for (i = 0; i < count; i++) {
- if (!service->set->reuse_port || listeners[i]->fd == -1)
- continue;
-
- old_fd = listeners[i]->fd;
- listeners[i]->fd = -1;
- if (service_listener_listen(listeners[i]) < 0)
- listeners[i]->fd = old_fd;
- }
-}
-
static int
service_unix_pid_listener_get_path(struct service_listener *l, pid_t pid,
string_t *path, const char **error_r)
}
static void
-service_dup_fds(struct service *service, int accepted_fd,
+service_dup_fds(struct service *service, unsigned int process_index,
+ int accepted_fd,
const struct service_listener *accepted_listener,
int *accepted_listener_fd_r)
{
}
}
+ /* The listeners array contains reuse_port=yes listeners
+ for every process. Here we filter out the listeners
+ not intended for this process. */
+ if (service->set->reuse_port &&
+ listeners[i]->reuse_port_process_index != process_index)
+ continue;
+
if (listeners[i] == accepted_listener)
*accepted_listener_fd_r = fd;
dup2_append(&dups, listeners[i]->fd, fd++);
env_put(MASTER_SERVICE_COUNT_ENV,
dec2str(service->set->restart_request_count));
}
+ if (service->set->reuse_port)
+ env_put(MASTER_REUSE_PORT_ENV, "1");
env_put(MASTER_UID_ENV, dec2str(uid));
env_put(MY_HOSTNAME_ENV, my_hostname);
env_put(MY_HOSTDOMAIN_ENV, hostdomain);
future lookups. */
hostdomain = my_hostdomain();
+ unsigned int process_index = 0;
+ if (service->set->reuse_port) {
+ /* Figure out the process index number. Do this by scanning
+ all the existing processes for the service and finding the
+ first nonexistent index number. Note that retired processes
+ are no longer listening, so their index must be reused. */
+ bool *seen_index = t_new(bool, service->process_limit);
+ struct service_process *p;
+ for (p = service->busy_processes; p != NULL; p = p->next) {
+ if (p->index < service->process_limit && !p->retired)
+ seen_index[p->index] = TRUE;
+ }
+ for (p = service->idle_processes_head; p != NULL; p = p->next) {
+ if (p->index < service->process_limit)
+ seen_index[p->index] = TRUE;
+ }
+ for (process_index = 0; process_index < service->process_limit; process_index++) {
+ if (!seen_index[process_index])
+ break;
+ }
+ i_assert(process_index < service->process_limit);
+ }
+
if (service->type == SERVICE_TYPE_ANVIL &&
service_anvil_global->pid != 0) {
pid = service_anvil_global->pid;
/* child */
int accepted_listener_fd;
service_process_setup_environment(service, uid, hostdomain);
- service_reopen_inet_listeners(service);
- service_dup_fds(service, accepted_fd, accepted_listener,
+ service_dup_fds(service, process_index, accepted_fd, accepted_listener,
&accepted_listener_fd);
if (accepted_fd != -1) {
i_assert(accepted_listener_fd > 0);
process->refcount = 1;
process->pid = pid;
process->uid = uid;
+ process->index = process_index;
process->create_time = ioloop_time;
if (process_forked) {
process->to_status =
smaller than the correct value. */
unsigned int total_count;
+ /* Process index number. This is set only for services with
+ inet_listener_reuse_port=yes listeners. See
+ service_listener.reuse_port_process_index for how this is used. */
+ unsigned int index;
+
/* Timestamp when the process was created */
time_t create_time;
/* Time when process started idling, or 0 if we're not idling. This is
return -1;
for (i = 0; i < ips_count; i++) {
- l = service_create_one_inet_listener(service, set,
- address, &ips[i]);
- array_push_back(&service->listeners, &l);
+ /* reuse_port=yes listeners create all of the processes'
+ listeners at startup. */
+ unsigned int j, count;
+ if (!service->set->reuse_port)
+ count = 1;
+ else
+ count = service->process_limit;
+ for (j = 0; j < count; j++) {
+ l = service_create_one_inet_listener(service, set,
+ address, &ips[i]);
+ l->reuse_port_process_index = j;
+ array_push_back(&service->listeners, &l);
+ }
}
service->have_inet_listeners = TRUE;
}
if (service->last_login_full_notify == all_processes_full ||
service->login_notify_fd == -1)
return;
+ if (service->set->reuse_port) {
+ /* With reuse_port=yes the processes don't care about sibling
+ processes' state. */
+ return;
+ }
/* change the state always immediately. it's cheap. */
service->last_login_full_notify = all_processes_full;
struct ip_addr ip;
} inetset;
} set;
+
+ /* This listener is used only by the process with this index number.
+ Each process gets their own reuse_port listener. This index number
+ is between 0..(process_limit-1) and valid only for that process
+ index. The index is assigned while at the process creation order.
+ If a process exits or retires, it is replaced by a new process with
+ the same index number. */
+ unsigned int reuse_port_process_index;
};
struct service {