changeset 10200:01676e67cf38 HEAD

master: anvil process now stays alive across SIGHUPs.
author Timo Sirainen <tss@iki.fi>
date Mon, 26 Oct 2009 23:45:18 -0400
parents 9d13e9f78d52
children b0017c5dd411
files src/master/main.c src/master/service-anvil.c src/master/service-anvil.h src/master/service-listen.c src/master/service-monitor.c src/master/service-process.c src/master/service.c src/master/service.h
diffstat 8 files changed, 216 insertions(+), 132 deletions(-) [+]
line wrap: on
line diff
--- a/src/master/main.c	Mon Oct 26 23:41:54 2009 -0400
+++ b/src/master/main.c	Mon Oct 26 23:45:18 2009 -0400
@@ -14,6 +14,7 @@
 #include "askpass.h"
 #include "capabilities.h"
 #include "service.h"
+#include "service-anvil.h"
 #include "service-listen.h"
 #include "service-monitor.h"
 #include "service-process.h"
@@ -295,6 +296,7 @@
 	const struct master_settings *set;
 	void **sets;
 	struct service_list *new_services;
+	struct service *service;
 	const char *error;
 
 	i_warning("SIGHUP received - reloading configuration");
@@ -334,7 +336,17 @@
 
 	/* switch to new configuration. */
 	services_monitor_stop(services);
-	(void)services_listen_using(new_services, services);
+	if (services_listen_using(new_services, services) < 0) {
+		services_monitor_start(services);
+		return;
+	}
+
+	/* anvil never dies. it just gets moved to the new services list */
+	service = service_lookup_type(services, SERVICE_TYPE_ANVIL);
+	if (service != NULL) {
+		while (service->processes != NULL)
+			service_process_destroy(service->processes);
+	}
 	services_destroy(services);
 
 	services = new_services;
@@ -416,6 +428,7 @@
 	i_free(pidfile_path);
 
 	services_destroy(services);
+	service_anvil_global_deinit();
 	service_pids_deinit();
 }
 
@@ -740,6 +753,7 @@
 	/* create service structures from settings. if there are any errors in
 	   service configuration we'll catch it here. */
 	service_pids_init();
+	service_anvil_global_init();
 	if (services_create(set, child_process_env, &services, &error) < 0)
 		i_fatal("%s", error);
 
--- a/src/master/service-anvil.c	Mon Oct 26 23:41:54 2009 -0400
+++ b/src/master/service-anvil.c	Mon Oct 26 23:45:18 2009 -0400
@@ -13,17 +13,18 @@
 
 #define ANVIL_HANDSHAKE "VERSION\tanvil\t1\t0\n"
 
+struct service_anvil_global *service_anvil_global;
+
 static void
-service_list_anvil_discard_input_stop(struct service_list *service_list)
+service_list_anvil_discard_input_stop(struct service_anvil_global *anvil)
 {
-	if (service_list->anvil_io_blocking != NULL) {
-		io_remove(&service_list->anvil_io_blocking);
-		io_remove(&service_list->anvil_io_nonblocking);
+	if (anvil->io_blocking != NULL) {
+		io_remove(&anvil->io_blocking);
+		io_remove(&anvil->io_nonblocking);
 	}
 }
 
-static void
-anvil_input_fd_discard(struct service_list *service_list, int fd)
+static void anvil_input_fd_discard(struct service_anvil_global *anvil, int fd)
 {
 	char buf[1024];
 	ssize_t ret;
@@ -31,30 +32,29 @@
 	ret = read(fd, buf, sizeof(buf));
 	if (ret <= 0) {
 		i_error("read(anvil fd) failed: %m");
-		service_list_anvil_discard_input_stop(service_list);
+		service_list_anvil_discard_input_stop(anvil);
 	}
 }
 
-static void anvil_input_blocking_discard(struct service_list *service_list)
+static void anvil_input_blocking_discard(struct service_anvil_global *anvil)
 {
-	anvil_input_fd_discard(service_list,
-			       service_list->blocking_anvil_fd[0]);
+	anvil_input_fd_discard(anvil, anvil->blocking_fd[0]);
+}
+
+static void anvil_input_nonblocking_discard(struct service_anvil_global *anvil)
+{
+	anvil_input_fd_discard(anvil, anvil->nonblocking_fd[0]);
 }
 
-static void anvil_input_nonblocking_discard(struct service_list *service_list)
+static void service_list_anvil_discard_input(struct service_anvil_global *anvil)
 {
-	anvil_input_fd_discard(service_list,
-			       service_list->nonblocking_anvil_fd[0]);
-}
+	if (anvil->io_blocking != NULL)
+		return;
 
-static void service_list_anvil_discard_input(struct service_list *service_list)
-{
-	service_list->anvil_io_blocking =
-		io_add(service_list->blocking_anvil_fd[0], IO_READ,
-		       anvil_input_blocking_discard, service_list);
-	service_list->anvil_io_nonblocking =
-		io_add(service_list->nonblocking_anvil_fd[0], IO_READ,
-		       anvil_input_nonblocking_discard, service_list);
+	anvil->io_blocking = io_add(anvil->blocking_fd[0], IO_READ,
+				    anvil_input_blocking_discard, anvil);
+	anvil->io_nonblocking = io_add(anvil->nonblocking_fd[0], IO_READ,
+				       anvil_input_nonblocking_discard, anvil);
 }
 
 static int anvil_send_handshake(int fd, const char **error_r)
@@ -89,71 +89,90 @@
 	return 0;
 }
 
-int service_list_init_anvil(struct service_list *service_list,
-			    const char **error_r)
+void service_anvil_monitor_start(struct service_list *service_list)
 {
-	if (pipe(service_list->blocking_anvil_fd) < 0) {
-		*error_r = t_strdup_printf("pipe() failed: %m");
-		return -1;
-	}
-	if (pipe(service_list->nonblocking_anvil_fd) < 0) {
-		(void)close(service_list->blocking_anvil_fd[0]);
-		(void)close(service_list->blocking_anvil_fd[1]);
-		*error_r = t_strdup_printf("pipe() failed: %m");
-		return -1;
+	struct service *service;
+
+	if (service_anvil_global->process_count == 0)
+		service_list_anvil_discard_input(service_anvil_global);
+	else {
+		service = service_lookup_type(service_list, SERVICE_TYPE_ANVIL);
+		service_process_create(service);
 	}
-	fd_set_nonblock(service_list->nonblocking_anvil_fd[1], TRUE);
-
-	fd_close_on_exec(service_list->blocking_anvil_fd[0], TRUE);
-	fd_close_on_exec(service_list->blocking_anvil_fd[1], TRUE);
-	fd_close_on_exec(service_list->nonblocking_anvil_fd[0], TRUE);
-	fd_close_on_exec(service_list->nonblocking_anvil_fd[1], TRUE);
-
-	i_assert(service_list->anvil_kills == NULL);
-	service_list->anvil_kills =
-		service_process_notify_init(service_list->nonblocking_anvil_fd[1],
-					    service_process_write_anvil_kill);
-	return 0;
 }
 
-void services_anvil_init(struct service_list *service_list)
+void service_anvil_process_created(struct service_process *process)
 {
-	/* this can't be in _init_anvil() because we can't do io_add()s
-	   before forking with kqueue. */
-	service_list_anvil_discard_input(service_list);
+	struct service_anvil_global *anvil = service_anvil_global;
+	const char *error;
+
+	service_anvil_global->pid = process->pid;
+	service_anvil_global->uid = process->uid;
+	service_anvil_global->process_count++;
+	service_list_anvil_discard_input_stop(anvil);
+
+	if (anvil_send_handshake(anvil->blocking_fd[1], &error) < 0 ||
+	    anvil_send_handshake(anvil->nonblocking_fd[1], &error) < 0)
+		service_error(process->service, "%s", error);
+}
+
+void service_anvil_process_destroyed(struct service_process *process)
+{
+	i_assert(service_anvil_global->process_count > 0);
+	if (--service_anvil_global->process_count == 0)
+		service_list_anvil_discard_input(service_anvil_global);
+
+	if (service_anvil_global->pid == process->pid)
+		service_anvil_global->pid = 0;
 }
 
-void service_list_deinit_anvil(struct service_list *service_list)
+void service_anvil_global_init(void)
 {
-	service_list_anvil_discard_input_stop(service_list);
-	service_process_notify_deinit(&service_list->anvil_kills);
-	if (close(service_list->blocking_anvil_fd[0]) < 0)
-		i_error("close(anvil) failed: %m");
-	if (close(service_list->blocking_anvil_fd[1]) < 0)
-		i_error("close(anvil) failed: %m");
-	if (close(service_list->nonblocking_anvil_fd[0]) < 0)
-		i_error("close(anvil) failed: %m");
-	if (close(service_list->nonblocking_anvil_fd[1]) < 0)
-		i_error("close(anvil) failed: %m");
-	service_list->blocking_anvil_fd[0] = -1;
+	struct service_anvil_global *anvil;
+
+	anvil = i_new(struct service_anvil_global, 1);
+	if (pipe(anvil->status_fd) < 0)
+		i_fatal("pipe() failed: %m");
+	if (pipe(anvil->blocking_fd) < 0)
+		i_fatal("pipe() failed: %m");
+	if (pipe(anvil->nonblocking_fd) < 0)
+		i_fatal("pipe() failed: %m");
+	fd_set_nonblock(anvil->status_fd[0], TRUE);
+	fd_set_nonblock(anvil->status_fd[1], TRUE);
+	fd_set_nonblock(anvil->nonblocking_fd[1], TRUE);
+
+	fd_close_on_exec(anvil->status_fd[0], TRUE);
+	fd_close_on_exec(anvil->status_fd[1], TRUE);
+	fd_close_on_exec(anvil->blocking_fd[0], TRUE);
+	fd_close_on_exec(anvil->blocking_fd[1], TRUE);
+	fd_close_on_exec(anvil->nonblocking_fd[0], TRUE);
+	fd_close_on_exec(anvil->nonblocking_fd[1], TRUE);
+
+	anvil->kills =
+		service_process_notify_init(anvil->nonblocking_fd[1],
+					    service_process_write_anvil_kill);
+	service_anvil_global = anvil;
 }
 
-void service_anvil_process_created(struct service *service)
+void service_anvil_global_deinit(void)
 {
-	struct service_list *list = service->list;
-	const char *error;
-
-	service_list_anvil_discard_input_stop(service->list);
+	struct service_anvil_global *anvil = service_anvil_global;
 
-	if (anvil_send_handshake(list->blocking_anvil_fd[1], &error) < 0 ||
-	    anvil_send_handshake(list->nonblocking_anvil_fd[1], &error) < 0)
-		service_error(service, "%s", error);
-}
+	service_list_anvil_discard_input_stop(anvil);
+	service_process_notify_deinit(&anvil->kills);
+	if (close(anvil->blocking_fd[0]) < 0)
+		i_error("close(anvil) failed: %m");
+	if (close(anvil->blocking_fd[1]) < 0)
+		i_error("close(anvil) failed: %m");
+	if (close(anvil->nonblocking_fd[0]) < 0)
+		i_error("close(anvil) failed: %m");
+	if (close(anvil->nonblocking_fd[1]) < 0)
+		i_error("close(anvil) failed: %m");
+	if (close(anvil->status_fd[0]) < 0)
+		i_error("close(anvil) failed: %m");
+	if (close(anvil->status_fd[1]) < 0)
+		i_error("close(anvil) failed: %m");
+	i_free(anvil);
 
-void service_anvil_process_destroyed(struct service *service)
-{
-	if (service->process_count == 0 &&
-	    service->list->anvil_io_blocking == NULL &&
-	    service->list->blocking_anvil_fd[0] != -1)
-		service_list_anvil_discard_input(service->list);
+	service_anvil_global = NULL;
 }
--- a/src/master/service-anvil.h	Mon Oct 26 23:41:54 2009 -0400
+++ b/src/master/service-anvil.h	Mon Oct 26 23:45:18 2009 -0400
@@ -1,12 +1,30 @@
 #ifndef SERVICE_ANVIL_H
 #define SERVICE_ANVIL_H
 
-int service_list_init_anvil(struct service_list *service_list,
-			    const char **error_r);
-void service_list_deinit_anvil(struct service_list *service_list);
-void services_anvil_init(struct service_list *service_list);
+struct service_anvil_global {
+	pid_t pid;
+	unsigned int uid;
+
+	int status_fd[2];
+	/* passed to child processes */
+	int blocking_fd[2];
+	/* used by master process to notify about dying processes */
+	int nonblocking_fd[2];
+
+	struct service_process_notify *kills;
+	struct io *io_blocking, *io_nonblocking;
 
-void service_anvil_process_created(struct service *service);
-void service_anvil_process_destroyed(struct service *service);
+	unsigned int process_count;
+};
+
+extern struct service_anvil_global *service_anvil_global;
+
+void service_anvil_monitor_start(struct service_list *service_list);
+
+void service_anvil_process_created(struct service_process *process);
+void service_anvil_process_destroyed(struct service_process *process);
+
+void service_anvil_global_init(void);
+void service_anvil_global_deinit(void);
 
 #endif
--- a/src/master/service-listen.c	Mon Oct 26 23:41:54 2009 -0400
+++ b/src/master/service-listen.c	Mon Oct 26 23:45:18 2009 -0400
@@ -211,12 +211,32 @@
 int services_listen_using(struct service_list *new_service_list,
 			  struct service_list *old_service_list)
 {
-	struct service *const *services;
+	struct service *const *services, *old_service, *new_service;
 	ARRAY_DEFINE(new_listeners_arr, struct service_listener *);
 	ARRAY_DEFINE(old_listeners_arr, struct service_listener *);
 	struct service_listener *const *new_listeners, *const *old_listeners;
 	unsigned int i, j, count, new_count, old_count;
 
+	/* rescue anvil's UNIX socket listener */
+	new_service = service_lookup_type(new_service_list, SERVICE_TYPE_ANVIL);
+	old_service = service_lookup_type(old_service_list, SERVICE_TYPE_ANVIL);
+	if (old_service != NULL && new_service != NULL) {
+		new_listeners = array_get(&new_service->listeners, &new_count);
+		old_listeners = array_get(&old_service->listeners, &old_count);
+		for (i = 0; i < old_count && i < new_count; i++) {
+			if (new_listeners[i]->type != old_listeners[i]->type)
+				break;
+		}
+		if (i != new_count && i != old_count) {
+			i_error("Can't change anvil's listeners on the fly");
+			return -1;
+		}
+		for (i = 0; i < new_count; i++) {
+			new_listeners[i]->fd = old_listeners[i]->fd;
+			old_listeners[i]->fd = -1;
+		}
+	}
+
 	/* first create an arrays of all listeners to make things easier */
 	t_array_init(&new_listeners_arr, 64);
 	services = array_get(&new_service_list->services, &count);
@@ -246,10 +266,11 @@
 
 	/* close what's left */
 	for (j = 0; j < old_count; j++) {
-		if (old_listeners[j]->fd != -1) {
-			if (close(old_listeners[j]->fd) < 0)
-				i_error("close(listener) failed: %m");
-		}
+		if (old_listeners[j]->fd == -1)
+			continue;
+
+		if (close(old_listeners[j]->fd) < 0)
+			i_error("close(listener) failed: %m");
 		switch (old_listeners[j]->type) {
 		case SERVICE_LISTENER_UNIX:
 		case SERVICE_LISTENER_FIFO: {
--- a/src/master/service-monitor.c	Mon Oct 26 23:41:54 2009 -0400
+++ b/src/master/service-monitor.c	Mon Oct 26 23:45:18 2009 -0400
@@ -309,8 +309,8 @@
 	struct service *const *services;
 	unsigned int i, count;
 
-	services_anvil_init(service_list);
 	services_log_init(service_list);
+	service_anvil_monitor_start(service_list);
 
 	services = array_get(&service_list->services, &count);
 	for (i = 0; i < count; i++) {
@@ -329,7 +329,8 @@
 			fd_close_on_exec(services[i]->status_fd[0], TRUE);
 			net_set_nonblock(services[i]->status_fd[1], TRUE);
 			fd_close_on_exec(services[i]->status_fd[1], TRUE);
-
+		}
+		if (services[i]->io_status == NULL) {
 			services[i]->io_status =
 				io_add(services[i]->status_fd[0], IO_READ,
 				       service_status_input, services[i]);
@@ -351,7 +352,8 @@
 	if (service->io_status != NULL)
 		io_remove(&service->io_status);
 
-	if (service->status_fd[0] != -1) {
+	if (service->status_fd[0] != -1 &&
+	    service->type != SERVICE_TYPE_ANVIL) {
 		for (i = 0; i < 2; i++) {
 			if (close(service->status_fd[i]) < 0) {
 				service_error(service,
@@ -395,8 +397,7 @@
 	if (process->total_count == 0)
 		service_monitor_throttle(service);
 
-	if (service->list->anvil_kills != NULL)
-		service_process_notify_add(service->list->anvil_kills, process);
+	service_process_notify_add(service_anvil_global->kills, process);
 }
 
 void services_monitor_reap_children(void)
@@ -424,6 +425,8 @@
 			service_process_failure(process, status);
 		}
 		service_destroyed = service->list->destroyed;
+		if (service->type == SERVICE_TYPE_ANVIL)
+			service_anvil_process_destroyed(process);
 		service_process_destroy(process);
 
 		if (!service_destroyed) {
--- a/src/master/service-process.c	Mon Oct 26 23:41:54 2009 -0400
+++ b/src/master/service-process.c	Mon Oct 26 23:45:18 2009 -0400
@@ -63,9 +63,9 @@
 	case SERVICE_TYPE_ANVIL:
 		/* nonblocking anvil fd must be the first one. anvil treats it
 		   as the master's fd */
-		dup2_append(&dups, service->list->nonblocking_anvil_fd[0],
+		dup2_append(&dups, service_anvil_global->nonblocking_fd[0],
 			    MASTER_LISTEN_FD_FIRST + n++);
-		dup2_append(&dups, service->list->blocking_anvil_fd[0],
+		dup2_append(&dups, service_anvil_global->blocking_fd[0],
 			    MASTER_LISTEN_FD_FIRST + n++);
 		socket_listener_count += 2;
 		break;
@@ -100,7 +100,7 @@
 		dup2_append(&dups, service->login_notify_fd,
 			    MASTER_LOGIN_NOTIFY_FD);
 	}
-	dup2_append(&dups, service->list->blocking_anvil_fd[1],
+	dup2_append(&dups, service_anvil_global->blocking_fd[1],
 		    MASTER_ANVIL_FD);
 	dup2_append(&dups, service->status_fd[1], MASTER_STATUS_FD);
 
@@ -231,13 +231,23 @@
 	struct service_process *process;
 	unsigned int uid = ++uid_counter;
 	pid_t pid;
+	bool process_forked;
 
 	if (service->to_throttle != NULL) {
 		/* throttling service, don't create new processes */
 		return NULL;
 	}
 
-	pid = fork();
+	if (service->type == SERVICE_TYPE_ANVIL &&
+	    service_anvil_global->pid != 0) {
+		pid = service_anvil_global->pid;
+		uid = service_anvil_global->uid;
+		process_forked = FALSE;
+	} else {
+		pid = fork();
+		process_forked = TRUE;
+	}
+
 	if (pid < 0) {
 		service_error(service, "fork() failed: %m");
 		return NULL;
@@ -250,30 +260,27 @@
 		process_exec(service->executable, NULL);
 	}
 
-	switch (service->type) {
-	case SERVICE_TYPE_ANVIL:
-		service_anvil_process_created(service);
-		/* fall through */
-	default:
-		process = i_new(struct service_process, 1);
-		process->service = service;
-		break;
-	}
-
-	DLLIST_PREPEND(&service->processes, process);
+	process = i_new(struct service_process, 1);
+	process->service = service;
 	process->refcount = 1;
 	process->pid = pid;
 	process->uid = uid;
-	process->to_status =
-		timeout_add(SERVICE_FIRST_STATUS_TIMEOUT_SECS * 1000,
-			    service_process_status_timeout, process);
+	if (process_forked) {
+		process->to_status =
+			timeout_add(SERVICE_FIRST_STATUS_TIMEOUT_SECS * 1000,
+				    service_process_status_timeout, process);
+	}
 
 	process->available_count = service->client_limit;
 	service->process_count++;
 	service->process_avail++;
+	DLLIST_PREPEND(&service->processes, process);
 
 	service_list_ref(service->list);
 	hash_table_insert(service_pids, &process->pid, process);
+
+	if (service->type == SERVICE_TYPE_ANVIL && process_forked)
+		service_anvil_process_created(process);
 	return process;
 }
 
@@ -294,15 +301,6 @@
 		timeout_remove(&process->to_status);
 	if (process->to_idle != NULL)
 		timeout_remove(&process->to_idle);
-
-	switch (process->service->type) {
-	case SERVICE_TYPE_ANVIL:
-		service_anvil_process_destroyed(service);
-		break;
-	default:
-		break;
-	}
-
 	if (service->list->log_byes != NULL)
 		service_process_notify_add(service->list->log_byes, process);
 
--- a/src/master/service.c	Mon Oct 26 23:41:54 2009 -0400
+++ b/src/master/service.c	Mon Oct 26 23:45:18 2009 -0400
@@ -245,6 +245,11 @@
 	service->log_process_internal_fd = -1;
 	service->login_notify_fd = -1;
 
+	if (service->type == SERVICE_TYPE_ANVIL) {
+		service->status_fd[0] = service_anvil_global->status_fd[0];
+		service->status_fd[1] = service_anvil_global->status_fd[1];
+	}
+
 	if (array_is_created(&set->unix_listeners))
 		unix_listeners = array_get(&set->unix_listeners, &unix_count);
 	else {
@@ -325,6 +330,20 @@
 	return NULL;
 }
 
+struct service *
+service_lookup_type(struct service_list *service_list, enum service_type type)
+{
+	struct service *const *services;
+	unsigned int i, count;
+
+	services = array_get(&service_list->services, &count);
+	for (i = 0; i < count; i++) {
+		if (services[i]->type == type)
+			return services[i];
+	}
+	return NULL;
+}
+
 static bool service_want(struct service_settings *set)
 {
 	char *const *proto;
@@ -407,9 +426,6 @@
 		return -1;
 	}
 
-	if (service_list_init_anvil(service_list, error_r) < 0)
-		return -1;
-
 	*services_r = service_list;
 	return 0;
 }
@@ -518,7 +534,6 @@
         services_monitor_reap_children();
 
 	services_monitor_stop(service_list);
-	service_list_deinit_anvil(service_list);
 
 	if (service_list->refcount > 1 &&
 	    service_list->service_set->shutdown_clients) {
--- a/src/master/service.h	Mon Oct 26 23:41:54 2009 -0400
+++ b/src/master/service.h	Mon Oct 26 23:45:18 2009 -0400
@@ -116,13 +116,6 @@
 	int master_log_fd[2];
 	struct service_process_notify *log_byes;
 
-	/* passed to child processes */
-	int blocking_anvil_fd[2];
-	/* used by master process to notify about dying processes */
-	int nonblocking_anvil_fd[2];
-	struct service_process_notify *anvil_kills;
-	struct io *anvil_io_blocking, *anvil_io_nonblocking;
-
 	ARRAY_DEFINE(services, struct service *);
 
 	unsigned int destroyed:1;
@@ -159,9 +152,12 @@
 void services_throttle_time_sensitives(struct service_list *list,
 				       unsigned int secs);
 
-/* Find a service by name. */
+/* Find service by name. */
 struct service *
 service_lookup(struct service_list *service_list, const char *name);
+/* Find service by type */
+struct service *
+service_lookup_type(struct service_list *service_list, enum service_type type);
 
 void service_error(struct service *service, const char *format, ...)
 	ATTR_FORMAT(2, 3);