changeset 9923:77228b5431e1 HEAD

*-login: Fixed dropping oldest connection when reaching all limits.
author Timo Sirainen <tss@iki.fi>
date Thu, 10 Sep 2009 18:56:49 -0400
parents 2e94a44c34ff
children 281ea23515ce
files src/lib-master/master-auth.c src/lib-master/master-interface.h src/lib-master/master-service-private.h src/lib-master/master-service.c src/lib-master/master-service.h src/login-common/client-common.c src/login-common/login-settings.c src/login-common/login-settings.h src/login-common/main.c src/master/service-auth-source.c src/master/service-auth-source.h src/master/service-monitor.c src/master/service-process.c src/master/service-process.h src/master/service.h
diffstat 15 files changed, 154 insertions(+), 67 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-master/master-auth.c	Thu Sep 10 18:54:14 2009 -0400
+++ b/src/lib-master/master-auth.c	Thu Sep 10 18:56:49 2009 -0400
@@ -103,11 +103,29 @@
 	auth->free_nodes = node;
 }
 
+static void
+master_notify_have_more_avail_processes(struct master_service *service,
+					bool have_more)
+{
+	if (!have_more) {
+		/* make sure we're listening for more connections */
+		master_service_io_listeners_add(service);
+	}
+	service->call_avail_overflow = !have_more;
+}
+
 static void request_handle(struct master_auth *auth,
 			   struct master_auth_reply *reply)
 {
         struct master_auth_request_node *node;
 
+	if (reply->tag == 0) {
+		/* notification from master */
+		master_notify_have_more_avail_processes(auth->service,
+							reply->status == 0);
+		return;
+	}
+
 	node = hash_table_lookup(auth->requests, POINTER_CAST(reply->tag));
 	if (node == NULL)
 		i_error("Master sent reply with unknown tag %u", reply->tag);
--- a/src/lib-master/master-interface.h	Thu Sep 10 18:54:14 2009 -0400
+++ b/src/lib-master/master-interface.h	Thu Sep 10 18:56:49 2009 -0400
@@ -64,6 +64,7 @@
 };
 
 struct master_auth_reply {
+	/* tag=0 are notifications from master */
 	unsigned int tag;
 	enum master_auth_status status;
 	/* PID of the post-login mail process handling this connection */
--- a/src/lib-master/master-service-private.h	Thu Sep 10 18:54:14 2009 -0400
+++ b/src/lib-master/master-service-private.h	Thu Sep 10 18:56:49 2009 -0400
@@ -34,6 +34,8 @@
 	unsigned int total_available_count;
 	struct master_status master_status;
 
+	void (*avail_overflow_callback)(void);
+
         struct master_auth *auth;
 	master_service_connection_callback_t *callback;
 
@@ -46,6 +48,9 @@
 	unsigned int initial_status_sent:1;
 	unsigned int default_settings:1;
 	unsigned int die_with_master:1;
+	unsigned int call_avail_overflow:1;
 };
 
+void master_service_io_listeners_add(struct master_service *service);
+
 #endif
--- a/src/lib-master/master-service.c	Thu Sep 10 18:54:14 2009 -0400
+++ b/src/lib-master/master-service.c	Thu Sep 10 18:56:49 2009 -0400
@@ -28,7 +28,6 @@
 
 struct master_service *master_service;
 
-static void io_listeners_add(struct master_service *service);
 static void io_listeners_remove(struct master_service *service);
 static void master_status_update(struct master_service *service);
 
@@ -294,7 +293,7 @@
 		master_service_set_service_count(service, 1);
 	}
 
-	io_listeners_add(service);
+	master_service_io_listeners_add(service);
 
 	if ((service->flags & MASTER_SERVICE_FLAG_STD_CLIENT) != 0) {
 		/* we already have a connection to be served */
@@ -380,6 +379,12 @@
 	return service->socket_count;
 }
 
+void master_service_set_avail_overflow_callback(struct master_service *service,
+						void (*callback)(void))
+{
+	service->avail_overflow_callback = callback;
+}
+
 const char *master_service_get_config_path(struct master_service *service)
 {
 	return service->config_path;
@@ -433,7 +438,7 @@
 void master_service_client_connection_destroyed(struct master_service *service)
 {
 	/* we can listen again */
-	io_listeners_add(service);
+	master_service_io_listeners_add(service);
 
 	i_assert(service->total_available_count > 0);
 
@@ -497,12 +502,20 @@
 
 static void master_service_listen(struct master_service_listener *l)
 {
+	struct master_service *service = l->service;
 	struct master_service_connection conn;
 
-	if (l->service->master_status.available_count == 0) {
-		/* we are full. stop listening for now. */
-		io_listeners_remove(l->service);
-		return;
+	if (service->master_status.available_count == 0) {
+		/* we are full. stop listening for now, unless overflow
+		   callback destroys one of the existing connections */
+		if (service->call_avail_overflow &&
+		    service->avail_overflow_callback != NULL)
+			service->avail_overflow_callback();
+
+		if (service->master_status.available_count == 0) {
+			io_listeners_remove(service);
+			return;
+		}
 	}
 
 	memset(&conn, 0, sizeof(conn));
@@ -514,7 +527,7 @@
 
 		if (errno != ENOTSOCK) {
 			i_error("net_accept() failed: %m");
-			master_service_error(l->service);
+			master_service_error(service);
 			return;
 		}
 		/* it's not a socket. probably a fifo. use the "listener"
@@ -529,10 +542,10 @@
 	conn.ssl = l->ssl;
 	net_set_nonblock(conn.fd, TRUE);
 
-	l->service->master_status.available_count--;
-        master_status_update(l->service);
+	service->master_status.available_count--;
+        master_status_update(service);
 
-        l->service->callback(&conn);
+        service->callback(&conn);
 }
 
 static void io_listeners_init(struct master_service *service)
@@ -556,7 +569,7 @@
 	}
 }
 
-static void io_listeners_add(struct master_service *service)
+void master_service_io_listeners_add(struct master_service *service)
 {
 	unsigned int i;
 
--- a/src/lib-master/master-service.h	Thu Sep 10 18:54:14 2009 -0400
+++ b/src/lib-master/master-service.h	Thu Sep 10 18:56:49 2009 -0400
@@ -56,6 +56,11 @@
    Normally all existing clients are handled first. */
 void master_service_set_die_with_master(struct master_service *service,
 					bool set);
+/* Call the given callback when there are no available connections and master
+   has indicated that it can't create any more processes to handle requests.
+   The callback could decide to kill one of the existing connections. */
+void master_service_set_avail_overflow_callback(struct master_service *service,
+						void (*callback)(void));
 
 /* Set maximum number of clients we can handle. Default is given by master. */
 void master_service_set_client_limit(struct master_service *service,
--- a/src/login-common/client-common.c	Thu Sep 10 18:54:14 2009 -0400
+++ b/src/login-common/client-common.c	Thu Sep 10 18:56:49 2009 -0400
@@ -19,12 +19,7 @@
 
 #include <stdlib.h>
 
-/* When max. number of simultaneous connections is reached, few of the
-   oldest connections are disconnected. Since we have to go through all of the
-   clients, it's faster if we disconnect multiple clients. */
-#define CLIENT_DESTROY_OLDEST_COUNT 16
-
-struct client *clients = NULL;
+struct client *clients = NULL, *last_client = NULL;
 static unsigned int clients_count = 0;
 
 static void client_idle_disconnect_timeout(struct client *client)
@@ -51,12 +46,6 @@
 
 	i_assert(fd != -1);
 
-	if (clients_get_count() >= set->login_max_connections) {
-		/* reached max. users count, kill few of the
-		   oldest connections */
-		client_destroy_oldest();
-	}
-
 	/* always use nonblocking I/O */
 	net_set_nonblock(fd, TRUE);
 
@@ -80,6 +69,8 @@
 	client->secured = ssl || client->trusted ||
 		net_ip_compare(remote_ip, local_ip);
 
+	if (last_client == NULL)
+		last_client = client;
 	DLLIST_PREPEND(&clients, client);
 	clients_count++;
 
@@ -114,6 +105,10 @@
 
 	i_assert(clients_count > 0);
 	clients_count--;
+	if (last_client == client) {
+		i_assert(client->prev != NULL || clients_count == 0);
+		last_client = client->prev;
+	}
 	DLLIST_REMOVE(&clients, client);
 
 	if (client->input != NULL)
@@ -213,39 +208,24 @@
 
 void client_destroy_oldest(void)
 {
-	unsigned int max_connections =
-		global_login_settings->login_max_connections;
 	struct client *client;
-	struct client *destroy_buf[CLIENT_DESTROY_OLDEST_COUNT];
-	unsigned int i, destroy_count;
-
-	/* find the oldest clients and put them to destroy-buffer */
-	memset(destroy_buf, 0, sizeof(destroy_buf));
 
-	destroy_count = max_connections > CLIENT_DESTROY_OLDEST_COUNT*2 ?
-		CLIENT_DESTROY_OLDEST_COUNT : I_MIN(max_connections/2, 1);
-	for (client = clients; client != NULL; client = client->next) {
-		for (i = 0; i < destroy_count; i++) {
-			if (destroy_buf[i] == NULL ||
-			    destroy_buf[i]->created > client->created) {
-				/* @UNSAFE */
-				memmove(destroy_buf+i+1, destroy_buf+i,
-					sizeof(destroy_buf) -
-					(i+1) * sizeof(destroy_buf[0]));
-				destroy_buf[i] = client;
-				break;
-			}
-		}
+	if (last_client == NULL) {
+		/* we have no clients */
+		return;
 	}
 
-	/* then kill them */
-	for (i = 0; i < destroy_count; i++) {
-		if (destroy_buf[i] == NULL)
+	/* destroy the last client that hasn't successfully authenticated yet.
+	   this is usually the last client, but don't kill it if it's just
+	   waiting for master to finish its job. */
+	for (client = last_client; client != NULL; client = client->prev) {
+		if (client->master_tag == 0)
 			break;
+	}
+	if (client == NULL)
+		client = last_client;
 
-		client_destroy(destroy_buf[i],
-			       "Disconnected: Connection queue full");
-	}
+	client_destroy(client, "Disconnected: Connection queue full");
 }
 
 void clients_destroy_all(void)
--- a/src/login-common/login-settings.c	Thu Sep 10 18:54:14 2009 -0400
+++ b/src/login-common/login-settings.c	Thu Sep 10 18:56:49 2009 -0400
@@ -39,7 +39,6 @@
 	DEF(SET_BOOL, auth_debug),
 	DEF(SET_BOOL, verbose_proctitle),
 
-	DEF(SET_UINT, login_max_connections),
 	DEF(SET_UINT, mail_max_userip_connections),
 
 	SETTING_DEFINE_LIST_END
@@ -70,7 +69,6 @@
 	MEMBER(auth_debug) FALSE,
 	MEMBER(verbose_proctitle) FALSE,
 
-	MEMBER(login_max_connections) 256,
 	MEMBER(mail_max_userip_connections) 10
 };
 
@@ -132,10 +130,6 @@
 		/* if we require valid cert, make sure we also ask for it */
 		set->ssl_verify_client_cert = TRUE;
 	}
-	if (set->login_max_connections < 1) {
-		*error_r = "login_max_connections must be at least 1";
-		return FALSE;
-	}
 
 	if (strcmp(set->ssl, "no") == 0) {
 		/* disabled */
--- a/src/login-common/login-settings.h	Thu Sep 10 18:54:14 2009 -0400
+++ b/src/login-common/login-settings.h	Thu Sep 10 18:56:49 2009 -0400
@@ -27,7 +27,6 @@
 	bool auth_debug;
 	bool verbose_proctitle;
 
-	unsigned int login_max_connections;
 	unsigned int mail_max_userip_connections;
 
 	/* generated: */
--- a/src/login-common/main.c	Thu Sep 10 18:54:14 2009 -0400
+++ b/src/login-common/main.c	Thu Sep 10 18:56:49 2009 -0400
@@ -101,10 +101,17 @@
 
 	/* set the number of fds we want to use. it may get increased or
 	   decreased. leave a couple of extra fds for auth sockets and such.
-	   normal connections each use one fd, but SSL connections use two */
+
+	   worst case each connection can use:
+
+	    - 1 for client
+	    - 1 for login proxy
+	    - 2 for client-side ssl proxy
+	    - 2 for server-side ssl proxy (with login proxy)
+	*/
 	max_fds = MASTER_LISTEN_FD_FIRST + 16 +
 		master_service_get_socket_count(master_service) +
-		global_login_settings->login_max_connections*2;
+		master_service_get_client_limit(master_service)*6;
 	restrict_fd_limit(max_fds);
 	io_loop_set_max_fd_count(current_ioloop, max_fds);
 
@@ -127,6 +134,9 @@
 			i_fatal("chdir(login) failed: %m");
 	}
 
+	master_service_set_avail_overflow_callback(master_service,
+						   client_destroy_oldest);
+
 	auth_client = auth_client_new((unsigned int)getpid());
         auth_client_set_connect_notify(auth_client, auth_connect_notify, NULL);
 
--- a/src/master/service-auth-source.c	Thu Sep 10 18:54:14 2009 -0400
+++ b/src/master/service-auth-source.c	Thu Sep 10 18:56:49 2009 -0400
@@ -271,3 +271,32 @@
 
 	service_process_auth_source_close(process);
 }
+
+void service_processes_auth_source_notify(struct service *service,
+					  bool all_processes_created)
+{
+	struct hash_iterate_context *iter;
+	void *key, *value;
+	enum master_auth_status status;
+
+	i_assert(service->type == SERVICE_TYPE_AUTH_SOURCE);
+
+	status = all_processes_created ? 1 : 0;
+
+	iter = hash_table_iterate_init(service_pids);
+	while (hash_table_iterate(iter, &key, &value)) {
+		struct service_process *process = value;
+		struct service_process_auth_source *auth_process;
+
+		if (process->service != service)
+			continue;
+
+		auth_process = (struct service_process_auth_source *)process;
+		if (auth_process->last_notify_status != (int)status) {
+			auth_process->last_notify_status = (int)status;
+			service_process_auth_source_send_reply(auth_process,
+							       0, status);
+		}
+	}
+	hash_table_iterate_deinit(&iter);
+}
--- a/src/master/service-auth-source.h	Thu Sep 10 18:54:14 2009 -0400
+++ b/src/master/service-auth-source.h	Thu Sep 10 18:56:49 2009 -0400
@@ -11,4 +11,7 @@
 					    unsigned int tag,
 					    enum master_auth_status status);
 
+void service_processes_auth_source_notify(struct service *service,
+					  bool all_processes_created);
+
 #endif
--- a/src/master/service-monitor.c	Thu Sep 10 18:54:14 2009 -0400
+++ b/src/master/service-monitor.c	Thu Sep 10 18:56:49 2009 -0400
@@ -6,6 +6,7 @@
 #include "fd-close-on-exec.h"
 #include "hash.h"
 #include "service.h"
+#include "service-auth-source.h"
 #include "service-process.h"
 #include "service-process-notify.h"
 #include "service-log.h"
@@ -18,6 +19,7 @@
 
 #define SERVICE_PROCESS_KILL_IDLE_MSECS (1000*60)
 #define SERVICE_STARTUP_FAILURE_THROTTLE_SECS 60
+#define SERVICE_DROP_WARN_INTERVAL_SECS 60
 
 static void service_monitor_start_extra_avail(struct service *service);
 
@@ -162,6 +164,26 @@
 	service_throttle(service, SERVICE_STARTUP_FAILURE_THROTTLE_SECS);
 }
 
+static void service_drop_connections(struct service *service)
+{
+	if (service->last_drop_warning +
+	    SERVICE_DROP_WARN_INTERVAL_SECS < ioloop_time) {
+		service->last_drop_warning = ioloop_time;
+		i_warning("service(%s): process_limit reached, "
+			  "client connections are being dropped",
+			  service->set->name);
+	}
+	service->listen_pending = TRUE;
+	service_monitor_listen_stop(service);
+
+	if (service->type == SERVICE_TYPE_AUTH_SOURCE) {
+		/* reached process limit, notify processes that they
+		   need to start killing existing connections if they
+		   reach connection limit */
+		service_processes_auth_source_notify(service, TRUE);
+	}
+}
+
 static void service_accept(struct service *service)
 {
 	i_assert(service->process_avail == 0);
@@ -169,11 +191,7 @@
 	if (service->process_count == service->process_limit) {
 		/* we've reached our limits, new clients will have to
 		   wait until there are more processes available */
-		i_warning("service(%s): process_limit reached, "
-			  "client connections are being dropped",
-			  service->set->name);
-		service->listen_pending = TRUE;
-                service_monitor_listen_stop(service);
+		service_drop_connections(service);
 		return;
 	}
 
@@ -212,7 +230,9 @@
 	struct service_listener *const *listeners;
 	unsigned int i, count;
 
-	if (service->process_avail > 0)
+	if (service->process_avail > 0 ||
+	    (service->process_count == service->process_limit &&
+	     service->listen_pending))
 		return;
 
 	service->listening = TRUE;
--- a/src/master/service-process.c	Thu Sep 10 18:54:14 2009 -0400
+++ b/src/master/service-process.c	Thu Sep 10 18:56:49 2009 -0400
@@ -484,7 +484,7 @@
 	if (service->process_count >= service->process_limit) {
 		/* we should get here only with auth dest services */
 		i_warning("service(%s): process_limit reached, "
-			  "client connections are being dropped",
+			  "dropping this client connection",
 			  service->set->name);
 		return NULL;
 	}
@@ -594,6 +594,11 @@
 
 	process->destroyed = TRUE;
 	service_process_unref(process);
+
+	if (service->process_count < service->process_limit &&
+	    service->type == SERVICE_TYPE_AUTH_SOURCE)
+		service_processes_auth_source_notify(service, FALSE);
+
 	service_list_unref(service_list);
 }
 
--- a/src/master/service-process.h	Thu Sep 10 18:54:14 2009 -0400
+++ b/src/master/service-process.h	Thu Sep 10 18:56:49 2009 -0400
@@ -48,6 +48,8 @@
 struct service_process_auth_source {
 	struct service_process process;
 
+	int last_notify_status;
+
 	int auth_fd;
 	struct io *io_auth;
 	struct ostream *auth_output;
--- a/src/master/service.h	Thu Sep 10 18:54:14 2009 -0400
+++ b/src/master/service.h	Thu Sep 10 18:56:49 2009 -0400
@@ -89,6 +89,9 @@
 	   successful authentication. */
 	struct service *auth_dest_service;
 
+	/* Last time a "dropping client connections" warning was logged */
+	time_t last_drop_warning;
+
 	/* all processes are in use and new connections are coming */
 	unsigned int listen_pending:1;
 	/* service is currently listening for new connections */