Mercurial > dovecot > core-2.2
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 */