[dovecot-cvs] dovecot/src/master login-process.c, 1.75, 1.76 login-process.h, 1.7, 1.8 main.c, 1.89, 1.90 master-login-interface.h, 1.7, 1.8 master-settings.c, 1.133, 1.134 master-settings.h, 1.86, 1.87

cras at dovecot.org cras at dovecot.org
Sun Aug 6 23:05:39 EEST 2006


Update of /var/lib/cvs/dovecot/src/master
In directory talvi:/tmp/cvs-serv16539/src/master

Modified Files:
	login-process.c login-process.h main.c 
	master-login-interface.h master-settings.c master-settings.h 
Log Message:
Fixes to login process handling, especially with
login_process_per_connection=no. Removed login_max_logging_users setting
since it was somewhat weird in how it worked. Added login_max_connections to
replace it with login_process_per_connection=no, and with =yes its
functionality is now within login_max_processes_count.



Index: login-process.c
===================================================================
RCS file: /var/lib/cvs/dovecot/src/master/login-process.c,v
retrieving revision 1.75
retrieving revision 1.76
diff -u -d -r1.75 -r1.76
--- login-process.c	16 Jun 2006 18:13:43 -0000	1.75
+++ login-process.c	6 Aug 2006 20:05:36 -0000	1.76
@@ -21,16 +21,18 @@
 
 struct login_process {
 	struct login_group *group;
-	struct login_process *prev_nonlisten, *next_nonlisten;
+	struct login_process *prev_prelogin, *next_prelogin;
 	int refcount;
 
 	pid_t pid;
 	int fd;
 	struct io *io;
 	struct ostream *output;
+	enum master_login_state state;
+
 	unsigned int initialized:1;
-	unsigned int listening:1;
 	unsigned int destroyed:1;
+	unsigned int inetd_child:1;
 };
 
 struct login_auth_request {
@@ -114,30 +116,79 @@
 	i_free(request);
 }
 
-static void login_process_mark_nonlistening(struct login_process *p)
+static void process_remove_from_prelogin_lists(struct login_process *p)
 {
-	if (!p->listening) {
-		i_error("login: received another \"not listening\" "
-			"notification (if you can't login at all, "
-			"see src/lib/fdpass.c)");
+	if (p->state != LOGIN_STATE_FULL_PRELOGINS)
 		return;
-	}
 
-	p->listening = FALSE;
+	if (p->prev_prelogin == NULL)
+		p->group->oldest_prelogin_process = p->next_prelogin;
+	else
+		p->prev_prelogin->next_prelogin = p->next_prelogin;
 
-	if (p->group != NULL) {
+	if (p->next_prelogin == NULL)
+		p->group->newest_prelogin_process = p->prev_prelogin;
+	else
+		p->next_prelogin->prev_prelogin = p->prev_prelogin;
+
+	p->prev_prelogin = p->next_prelogin = NULL;
+}
+
+static void process_mark_nonlistening(struct login_process *p,
+				      enum master_login_state new_state)
+{
+	if (p->group == NULL)
+		return;
+
+	if (p->state == LOGIN_STATE_LISTENING)
 		p->group->listening_processes--;
-		p->prev_nonlisten = p->group->newest_nonlisten_process;
 
-		if (p->group->newest_nonlisten_process != NULL)
-			p->group->newest_nonlisten_process->next_nonlisten = p;
-		p->group->newest_nonlisten_process = p;
+	if (new_state == LOGIN_STATE_FULL_PRELOGINS) {
+		/* add to prelogin list */
+		i_assert(p->state != new_state);
 
-		if (p->group->oldest_nonlisten_process == NULL)
-			p->group->oldest_nonlisten_process = p;
+		p->prev_prelogin = p->group->newest_prelogin_process;
+		if (p->group->newest_prelogin_process == NULL)
+			p->group->oldest_prelogin_process = p;
+		else
+			p->group->newest_prelogin_process->next_prelogin = p;
+		p->group->newest_prelogin_process = p;
+	} else {
+		process_remove_from_prelogin_lists(p);
 	}
 }
 
+static void process_mark_listening(struct login_process *p)
+{
+	if (p->group == NULL)
+		return;
+
+	if (p->state != LOGIN_STATE_LISTENING)
+		p->group->listening_processes++;
+
+	process_remove_from_prelogin_lists(p);
+}
+
+static void
+login_process_set_state(struct login_process *p, enum master_login_state state)
+{
+	if (state == p->state || state > LOGIN_STATE_COUNT ||
+	    (state < p->state && p->group->set->login_process_per_connection)) {
+		i_error("login: tried to change state %d -> %d "
+			"(if you can't login at all, see src/lib/fdpass.c)",
+			p->state, state);
+		return;
+	}
+
+	if (state == LOGIN_STATE_LISTENING) {
+		process_mark_listening(p);
+	} else {
+		process_mark_nonlistening(p, state);
+	}
+
+	p->state = state;
+}
+
 static void login_process_groups_create(void)
 {
 	struct server_settings *server;
@@ -271,12 +322,14 @@
 
 	if (client_fd == -1) {
 		/* just a notification that the login process */
+		enum master_login_state state = req.tag;
+
 		if (!p->initialized) {
 			/* initialization notify */
 			p->initialized = TRUE;;
 		} else {
-			/* not listening for new connections anymore */
-			login_process_mark_nonlistening(p);
+			/* change "listening for new connections" status */
+			login_process_set_state(p, state);
 		}
 		return;
 	}
@@ -316,7 +369,6 @@
 	p->refcount = 1;
 	p->pid = pid;
 	p->fd = fd;
-	p->listening = TRUE;
 	p->io = io_add(fd, IO_READ, login_process_input, p);
 	p->output = o_stream_create_file(fd, default_pool,
 					 sizeof(struct master_login_reply)*10,
@@ -325,6 +377,8 @@
 	PID_ADD_PROCESS_TYPE(pid, PROCESS_TYPE_LOGIN);
 	hash_insert(processes, POINTER_CAST(pid), p);
 
+	p->state = LOGIN_STATE_LISTENING;
+
 	if (p->group != NULL) {
 		p->group->processes++;
 		p->group->listening_processes++;
@@ -332,22 +386,13 @@
 	return p;
 }
 
-static void login_process_remove_from_lists(struct login_process *p)
+static void login_process_exited(struct login_process *p)
 {
-	if (p->group == NULL)
-		return;
-
-	if (p == p->group->oldest_nonlisten_process)
-		p->group->oldest_nonlisten_process = p->next_nonlisten;
-	else
-		p->prev_nonlisten->next_nonlisten = p->next_nonlisten;
-
-	if (p == p->group->newest_nonlisten_process)
-		p->group->newest_nonlisten_process = p->prev_nonlisten;
-	else
-		p->next_nonlisten->prev_nonlisten = p->prev_nonlisten;
+	if (p->group != NULL)
+		p->group->processes--;
 
-	p->next_nonlisten = p->prev_nonlisten = NULL;
+	hash_remove(processes, POINTER_CAST(p->pid));
+	login_process_unref(p);
 }
 
 static void login_process_destroy(struct login_process *p)
@@ -361,24 +406,15 @@
 		io_loop_stop(ioloop);
 	}
 
-	if (p->listening && p->group != NULL)
-		p->group->listening_processes--;
-
 	o_stream_close(p->output);
 	io_remove(&p->io);
 	if (close(p->fd) < 0)
 		i_error("close(login) failed: %m");
 
-	if (!p->listening)
-		login_process_remove_from_lists(p);
-
-	if (p->group != NULL)
-		p->group->processes--;
-
-	if (p->pid != 0)
-		hash_remove(processes, POINTER_CAST(p->pid));
+	process_mark_nonlistening(p, LOGIN_STATE_FULL_LOGINS);
 
-	login_process_unref(p);
+	if (p->inetd_child)
+		login_process_exited(p);
 }
 
 static void login_process_unref(struct login_process *p)
@@ -445,8 +481,8 @@
 		env_put("PROCESS_PER_CONNECTION=1");
 		env_put("MAX_LOGGING_USERS=1");
 	} else {
-		env_put(t_strdup_printf("MAX_LOGGING_USERS=%u",
-					set->login_max_logging_users));
+		env_put(t_strdup_printf("MAX_CONNECTIONS=%u",
+					set->login_max_connections));
 	}
 
 	env_put(t_strconcat("PROCESS_UID=", dec2str(pid), NULL));
@@ -473,13 +509,6 @@
 	pid_t pid;
 	int fd[2], log_fd;
 
-	if (group->set->login_process_per_connection &&
-	    group->processes - group->listening_processes >=
-	    group->set->login_max_logging_users) {
-		if (group->oldest_nonlisten_process != NULL)
-			login_process_destroy(group->oldest_nonlisten_process);
-	}
-
 	if (group->set->login_uid == 0)
 		i_fatal("Login process must not run as root");
 
@@ -569,25 +598,36 @@
 	return -1;
 }
 
-void login_process_abormal_exit(pid_t pid)
+void login_process_destroyed(pid_t pid, bool abnormal_exit)
 {
 	struct login_process *p;
 
-	/* don't start raising the process count if they're dying all
-	   the time */
 	p = hash_lookup(processes, POINTER_CAST(pid));
-	if (p != NULL && p->group != NULL)
-		p->group->wanted_processes_count = 0;
+	if (p == NULL)
+		i_panic("Lost login process PID %s", dec2str(pid));
+	i_assert(!p->inetd_child);
+
+	if (abnormal_exit) {
+		/* don't start raising the process count if they're dying all
+		   the time */
+		if (p->group != NULL)
+			p->group->wanted_processes_count = 0;
+	}
+
+	login_process_destroy(p);
+	login_process_exited(p);
 }
 
-void login_processes_destroy_all(void)
+void login_processes_destroy_all(bool unref)
 {
 	struct hash_iterate_context *iter;
 	void *key, *value;
 
 	iter = hash_iterate_init(processes);
-	while (hash_iterate(iter, &key, &value))
+	while (hash_iterate(iter, &key, &value)) {
 		login_process_destroy(value);
+		if (unref) login_process_unref(value);
+	}
 	hash_iterate_deinit(iter);
 
 	while (login_groups != NULL) {
@@ -598,17 +638,34 @@
 	}
 }
 
+static void login_processes_notify_group(struct login_group *group)
+{
+	struct hash_iterate_context *iter;
+	struct master_login_reply reply;
+	void *key, *value;
+
+	memset(&reply, 0, sizeof(reply));
+
+	iter = hash_iterate_init(processes);
+	while (hash_iterate(iter, &key, &value)) {
+		struct login_process *p = value;
+
+		if (p->group == group)
+			(void)o_stream_send(p->output, &reply, sizeof(reply));
+	}
+	hash_iterate_deinit(iter);
+}
+
 static int login_group_start_missings(struct login_group *group)
 {
-	if (!group->set->login_process_per_connection) {
-		/* create max. one process every second, that way if it keeps
-		   dying all the time we don't eat all cpu with fork()ing. */
-		if (group->listening_processes <
-		    group->set->login_processes_count) {
-			if (create_login_process(group) < 0)
-				return -1;
-		}
-		return 0;
+	if (group->set->login_process_per_connection &&
+	    group->processes >= group->set->login_max_processes_count &&
+	    group->listening_processes == 0) {
+		/* destroy the oldest listening process. non-listening
+		   processes are logged in users who we don't want to kick out
+		   because someone's started flooding */
+		if (group->oldest_prelogin_process != NULL)
+			login_process_destroy(group->oldest_prelogin_process);
 	}
 
 	/* we want to respond fast when multiple clients are connecting
@@ -627,16 +684,19 @@
 		 group->set->login_processes_count)
 		group->wanted_processes_count--;
 
-	if (group->wanted_processes_count >
-	    group->set->login_max_processes_count) {
-		group->wanted_processes_count =
-			group->set->login_max_processes_count;
-	}
-
-	while (group->listening_processes < group->wanted_processes_count) {
+	while (group->listening_processes < group->wanted_processes_count &&
+	       group->processes < group->set->login_max_processes_count) {
 		if (create_login_process(group) < 0)
 			return -1;
 	}
+
+	if (group->listening_processes == 0 &&
+	    !group->set->login_process_per_connection) {
+		/* we've reached our limit. notify the processes to start
+		   listening again which makes them kill some of their
+		   oldest clients when accepting the next connection */
+		login_processes_notify_group(group);
+	}
 	return 0;
 }
 
@@ -744,6 +804,7 @@
 
 		p = login_process_new(NULL, ++login_pid_counter, fd);
 		p->initialized = TRUE;
+		p->inetd_child = TRUE;
 	}
 }
 
@@ -771,6 +832,6 @@
 	if (io_listen != NULL)
 		io_remove(&io_listen);
 
-        login_processes_destroy_all();
+        login_processes_destroy_all(TRUE);
 	hash_destroy(processes);
 }

Index: login-process.h
===================================================================
RCS file: /var/lib/cvs/dovecot/src/master/login-process.h,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -d -r1.7 -r1.8
--- login-process.h	24 May 2004 22:33:51 -0000	1.7
+++ login-process.h	6 Aug 2006 20:05:36 -0000	1.8
@@ -11,12 +11,15 @@
 	unsigned int listening_processes;
 	unsigned int wanted_processes_count;
 
-	struct login_process *oldest_nonlisten_process;
-	struct login_process *newest_nonlisten_process;
+	/* if login_process_per_connection=yes this contains the list of
+	   processes that are in LOGIN_STATE_FULL_PRELOGINS state */
+	struct login_process *oldest_prelogin_process;
+	struct login_process *newest_prelogin_process;
 };
 
-void login_process_abormal_exit(pid_t pid);
-void login_processes_destroy_all(void);
+void login_process_destroyed(pid_t pid, bool abnormal_exit);
+
+void login_processes_destroy_all(bool unref);
 
 void login_processes_init(void);
 void login_processes_deinit(void);

Index: main.c
===================================================================
RCS file: /var/lib/cvs/dovecot/src/master/main.c,v
retrieving revision 1.89
retrieving revision 1.90
diff -u -d -r1.89 -r1.90
--- main.c	5 Jul 2006 19:52:22 -0000	1.89
+++ main.c	6 Aug 2006 20:05:36 -0000	1.90
@@ -37,7 +37,6 @@
 };
 
 static const char *configfile = SYSCONFDIR "/" PACKAGE ".conf";
-static struct timeout *to;
 static const char *env_tz;
 
 struct ioloop *ioloop;
@@ -134,7 +133,7 @@
 	i_warning("SIGHUP received - reloading configuration");
 
 	/* restart auth and login processes */
-        login_processes_destroy_all();
+        login_processes_destroy_all(FALSE);
         auth_processes_destroy_all();
         dict_process_kill();
 
@@ -191,38 +190,28 @@
 	return NULL;
 }
 
-static void timeout_handler(void *context __attr_unused__)
+static void sigchld_handler(int signo __attr_unused__,
+			    void *context __attr_unused__)
 {
 	const char *process_type_name, *msg;
 	pid_t pid;
 	int status, process_type;
+	bool abnormal_exit;
 
 	while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
 		/* get the type and remove from hash */
 		process_type = PID_GET_PROCESS_TYPE(pid);
 		PID_REMOVE_PROCESS_TYPE(pid);
 
-		switch (process_type) {
-		case PROCESS_TYPE_IMAP:
-		case PROCESS_TYPE_POP3:
-			mail_process_destroyed(pid);
-			break;
-		case PROCESS_TYPE_SSL_PARAM:
-			ssl_parameter_process_destroyed(pid);
-			break;
-		case PROCESS_TYPE_DICT:
-			dict_process_restart();
-			break;
-		}
+		abnormal_exit = TRUE;
 
 		/* write errors to syslog */
 		process_type_name = process_names[process_type];
 		if (WIFEXITED(status)) {
 			status = WEXITSTATUS(status);
-			if (status != 0) {
-				if (process_type == PROCESS_TYPE_LOGIN)
-					login_process_abormal_exit(pid);
-
+			if (status == 0)
+				abnormal_exit = FALSE;
+			else {
 				msg = get_exit_status_message(status);
 				msg = msg == NULL ? "" :
 					t_strconcat(" (", msg, ")", NULL);
@@ -231,12 +220,26 @@
 					status, msg);
 			}
 		} else if (WIFSIGNALED(status)) {
-			if (process_type == PROCESS_TYPE_LOGIN)
-				login_process_abormal_exit(pid);
 			i_error("child %s (%s) killed with signal %d",
 				dec2str(pid), process_type_name,
 				WTERMSIG(status));
 		}
+
+		switch (process_type) {
+		case PROCESS_TYPE_LOGIN:
+			login_process_destroyed(pid, abnormal_exit);
+			break;
+		case PROCESS_TYPE_IMAP:
+		case PROCESS_TYPE_POP3:
+			mail_process_destroyed(pid);
+			break;
+		case PROCESS_TYPE_SSL_PARAM:
+			ssl_parameter_process_destroyed(pid);
+			break;
+		case PROCESS_TYPE_DICT:
+			dict_process_restart();
+			break;
+		}
 	}
 
 	if (pid == -1 && errno != EINTR && errno != ECHILD)
@@ -558,7 +561,7 @@
         lib_signals_set_handler(SIGUSR1, TRUE, sig_reopen_logs, NULL);
 
 	pids = hash_create(default_pool, default_pool, 128, NULL, NULL);
-	to = timeout_add(100, timeout_handler, NULL);
+	lib_signals_set_handler(SIGCHLD, TRUE, sigchld_handler, NULL);
 
 	ssl_init();
 	dict_process_init();
@@ -575,14 +578,14 @@
 				 "/master.pid", NULL));
 
 	/* make sure we log if child processes died unexpectedly */
-	timeout_handler(NULL);
+	sigchld_handler(SIGCHLD, NULL);
 
 	login_processes_deinit();
 	auth_processes_deinit();
 	dict_process_deinit();
 	ssl_deinit();
 
-	timeout_remove(&to);
+	lib_signals_unset_handler(SIGCHLD, sigchld_handler, NULL);
 
 	if (close(null_fd) < 0)
 		i_error("close(null_fd) failed: %m");

Index: master-login-interface.h
===================================================================
RCS file: /var/lib/cvs/dovecot/src/master/master-login-interface.h,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -d -r1.7 -r1.8
--- master-login-interface.h	13 Jan 2006 20:26:40 -0000	1.7
+++ master-login-interface.h	6 Aug 2006 20:05:36 -0000	1.8
@@ -9,10 +9,23 @@
 
 /* Increase the version number every time master_login_request
    (or something else) is changed. */
-#define MASTER_LOGIN_PROTOCOL_VERSION 1
+#define MASTER_LOGIN_PROTOCOL_VERSION 2
+
+enum master_login_state {
+	/* process is accepting new connections */
+	LOGIN_STATE_LISTENING = 0,
+	/* process isn't accepting new connections, but it'd be able to kill
+	   some connections which haven't logged in yet */
+	LOGIN_STATE_FULL_PRELOGINS,
+	/* process is handling only logged in users */
+	LOGIN_STATE_FULL_LOGINS,
+
+	LOGIN_STATE_COUNT
+};
 
 struct master_login_request {
 	uint32_t version;
+	/* if fd == -1, tag is used as master_login_state */
 	uint32_t tag;
 
 	uint32_t auth_pid;

Index: master-settings.c
===================================================================
RCS file: /var/lib/cvs/dovecot/src/master/master-settings.c,v
retrieving revision 1.133
retrieving revision 1.134
diff -u -d -r1.133 -r1.134
--- master-settings.c	2 Jul 2006 19:19:35 -0000	1.133
+++ master-settings.c	6 Aug 2006 20:05:36 -0000	1.134
@@ -93,7 +93,7 @@
 	DEF(SET_INT, login_process_size),
 	DEF(SET_INT, login_processes_count),
 	DEF(SET_INT, login_max_processes_count),
-	DEF(SET_INT, login_max_logging_users),
+	DEF(SET_INT, login_max_connections),
 
 	/* mail */
 	DEF(SET_STR, valid_chroot_dirs),
@@ -299,7 +299,7 @@
 	MEMBER(login_process_size) 32,
 	MEMBER(login_processes_count) 3,
 	MEMBER(login_max_processes_count) 128,
-	MEMBER(login_max_logging_users) 256,
+	MEMBER(login_max_connections) 256,
 
 	/* mail */
 	MEMBER(valid_chroot_dirs) "",
@@ -846,8 +846,8 @@
 		i_error("login_processes_count must be at least 1");
 		return FALSE;
 	}
-	if (set->login_max_logging_users < 1) {
-		i_error("login_max_logging_users must be at least 1");
+	if (set->login_max_connections < 1) {
+		i_error("login_max_connections must be at least 1");
 		return FALSE;
 	}
 

Index: master-settings.h
===================================================================
RCS file: /var/lib/cvs/dovecot/src/master/master-settings.h,v
retrieving revision 1.86
retrieving revision 1.87
diff -u -d -r1.86 -r1.87
--- master-settings.h	28 Jun 2006 13:10:54 -0000	1.86
+++ master-settings.h	6 Aug 2006 20:05:36 -0000	1.87
@@ -54,7 +54,7 @@
 	unsigned int login_process_size;
 	unsigned int login_processes_count;
 	unsigned int login_max_processes_count;
-	unsigned int login_max_logging_users;
+	unsigned int login_max_connections;
 
 	/* mail */
 	const char *valid_chroot_dirs;



More information about the dovecot-cvs mailing list