[dovecot-cvs] dovecot/src/lib-sql driver-mysql.c,1.3,1.4

cras at dovecot.org cras at dovecot.org
Fri Jan 7 17:48:48 EET 2005


Update of /var/lib/cvs/dovecot/src/lib-sql
In directory talvi:/tmp/cvs-serv9791/lib-sql

Modified Files:
	driver-mysql.c 
Log Message:
Mysql driver supports connecting to multiple servers by giving multiple
host= parameters in connect string. The queries are sent round robin, and if
the query fails because of lost connection for one server it's retried on
next one.



Index: driver-mysql.c
===================================================================
RCS file: /var/lib/cvs/dovecot/src/lib-sql/driver-mysql.c,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -d -r1.3 -r1.4
--- driver-mysql.c	7 Jan 2005 14:39:22 -0000	1.3
+++ driver-mysql.c	7 Jan 2005 15:48:45 -0000	1.4
@@ -1,6 +1,7 @@
 /* Copyright (C) 2003-2004 Timo Sirainen, Alex Howansky */
 
 #include "lib.h"
+#include "buffer.h"
 #include "sql-api-private.h"
 
 #ifdef HAVE_MYSQL
@@ -9,23 +10,46 @@
 #include <mysql.h>
 #include <errmsg.h>
 
+/* Minimum delay between reconnecting to same server */
+#define CONNECT_MIN_DELAY 1
+/* Maximum time to avoiding reconnecting to same server */
+#define CONNECT_MAX_DELAY (60*30)
+/* If no servers are connected but a query is requested, try reconnecting to
+   next server which has been disconnected longer than this (with a single
+   server setup this is really the "max delay" and the CONNECT_MAX_DELAY
+   is never used). */
+#define CONNECT_RESET_DELAY 15
+
 struct mysql_db {
 	struct sql_db api;
 
 	pool_t pool;
-	const char *host, *user, *password, *dbname, *unix_socket;
+	const char *user, *password, *dbname, *unix_socket;
 	const char *ssl_cert, *ssl_key, *ssl_ca, *ssl_ca_path, *ssl_cipher;
 	unsigned int port, client_flags;
+
+	buffer_t *connections; /* struct mysql_connection[] */
+	unsigned int next_query_connection;
+};
+
+struct mysql_connection {
+	struct mysql_db *db;
+
 	MYSQL *mysql;
+	const char *host;
 
-	time_t last_connect;
+	unsigned int connect_delay;
+	unsigned int connect_failure_count;
 
+	time_t last_connect;
 	unsigned int connected:1;
-	unsigned int ssl:1;
+	unsigned int ssl_set:1;
 };
 
 struct mysql_result {
 	struct sql_result api;
+	struct mysql_connection *conn;
+
 	MYSQL_RES *result;
         MYSQL_ROW row;
 
@@ -36,42 +60,102 @@
 extern struct sql_result driver_mysql_result;
 extern struct sql_result driver_mysql_error_result;
 
-static int driver_mysql_connect(struct mysql_db *db)
+static int driver_mysql_connect(struct mysql_connection *conn)
 {
+	struct mysql_db *db = conn->db;
 	const char *unix_socket, *host;
 	time_t now;
 
-	if (db->connected)
+	if (conn->connected)
 		return TRUE;
 
 	/* don't try reconnecting more than once a second */
 	now = time(NULL);
-	if (db->last_connect == now)
+	if (conn->last_connect + (time_t)conn->connect_delay > now)
 		return FALSE;
-	db->last_connect = now;
+	conn->last_connect = now;
 
-	if (*db->host == '/') {
-		unix_socket = db->host;
+	if (*conn->host == '/') {
+		unix_socket = conn->host;
 		host = NULL;
 	} else {
 		unix_socket = NULL;
-		host = db->host;
+		host = conn->host;
 	}
 
-	if (mysql_real_connect(db->mysql, host, db->user, db->password,
+	if (!conn->ssl_set && (db->ssl_ca != NULL || db->ssl_ca_path != NULL)) {
+#ifdef HAVE_MYSQL_SSL
+		mysql_ssl_set(conn->mysql, db->ssl_key, db->ssl_cert,
+			      db->ssl_ca, db->ssl_ca_path
+#ifdef HAVE_MYSQL_SSL_CIPHER
+			      , db->ssl_cipher
+#endif
+			     );
+		conn->ssl_set = TRUE;
+#else
+		i_fatal("mysql: SSL support not compiled in "
+			"(remove ssl_ca and ssl_ca_path settings)");
+#endif
+	}
+
+	if (mysql_real_connect(conn->mysql, host, db->user, db->password,
 			       db->dbname, db->port, unix_socket,
 			       db->client_flags) == NULL) {
-		i_error("mysql: Connect failed to %s: %s",
-			db->dbname, mysql_error(db->mysql));
+		if (conn->connect_failure_count > 0) {
+			/* increase delay between reconnections to this
+			   server */
+			conn->connect_delay *= 5;
+			if (conn->connect_delay > CONNECT_MAX_DELAY)
+				conn->connect_delay = CONNECT_MAX_DELAY;
+		}
+		conn->connect_failure_count++;
+
+		i_error("mysql: Connect failed to %s (%s): %s - "
+			"waiting for %u seconds before retry",
+			host, db->dbname, mysql_error(conn->mysql),
+			conn->connect_delay);
 		return FALSE;
 	} else {
-		i_info("mysql: Connected to %s%s", db->dbname,
-		       db->ssl ? " using SSL" : "");
-		db->connected = TRUE;
+		i_info("mysql: Connected to %s%s (%s)", host,
+		       conn->ssl_set ? " using SSL" : "", db->dbname);
+
+		conn->connect_failure_count = 0;
+		conn->connect_delay = CONNECT_MIN_DELAY;
+		conn->connected = TRUE;
 		return TRUE;
 	}
 }
 
+static void driver_mysql_connect_all(struct mysql_db *db)
+{
+	struct mysql_connection *conn;
+	size_t i, size;
+
+	conn = buffer_get_modifyable_data(db->connections, &size);
+	size /= sizeof(*conn);
+	for (i = 0; i < size; i++)
+		(void)driver_mysql_connect(&conn[i]);
+}
+
+static void driver_mysql_connection_add(struct mysql_db *db, const char *host)
+{
+	struct mysql_connection *conn;
+
+	conn = buffer_append_space_unsafe(db->connections, sizeof(*conn));
+	conn->db = db;
+	conn->host = p_strdup(db->pool, host);
+	conn->mysql = mysql_init(NULL);
+	if (conn->mysql == NULL)
+		i_fatal("mysql_init() failed");
+
+	conn->connect_delay = CONNECT_MIN_DELAY;
+}
+
+static void driver_mysql_connection_free(struct mysql_connection *conn)
+{
+	mysql_close(conn->mysql);
+}
+
 static void driver_mysql_parse_connect_string(struct mysql_db *db,
 					      const char *connect_string)
 {
@@ -92,8 +176,9 @@
 		value++;
 
 		field = NULL;
-		if (strcmp(name, "host") == 0 || strcmp(name, "hostaddr") == 0)
-			field = &db->host;
+		if (strcmp(name, "host") == 0 ||
+		    strcmp(name, "hostaddr") == 0)
+			driver_mysql_connection_add(db, value);
 		else if (strcmp(name, "user") == 0)
 			field = &db->user;
 		else if (strcmp(name, "password") == 0)
@@ -121,6 +206,9 @@
 			*field = p_strdup(db->pool, value);
 	}
 	t_pop();
+
+	if (db->connections->used == 0)
+		i_fatal("mysql: No hosts given in connect string");
 }
 
 static struct sql_db *driver_mysql_init(const char *connect_string)
@@ -133,54 +221,47 @@
 	db = p_new(pool, struct mysql_db, 1);
 	db->pool = pool;
 	db->api = driver_mysql_db;
-	db->mysql = mysql_init(NULL);
-	if (db->mysql == NULL)
-		i_fatal("mysql_init() failed");
+	db->connections =
+		buffer_create_dynamic(pool,
+				      sizeof(struct mysql_connection) * 6);
 
 	driver_mysql_parse_connect_string(db, connect_string);
-	if (db->ssl_ca != NULL || db->ssl_ca_path != NULL) {
-#ifdef HAVE_MYSQL_SSL
-		mysql_ssl_set(db->mysql, db->ssl_key, db->ssl_cert,
-			      db->ssl_ca, db->ssl_ca_path
-#ifdef HAVE_MYSQL_SSL_CIPHER
-			      , db->ssl_cipher
-#endif
-			     );
-		db->ssl = TRUE;
-#else
-		i_fatal("mysql: SSL support not compiled in "
-			"(remove ssl_ca and ssl_ca_path settings)");
-#endif
-	}
-	(void)driver_mysql_connect(db);
+	driver_mysql_connect_all(db);
 	return &db->api;
 }
 
 static void driver_mysql_deinit(struct sql_db *_db)
 {
 	struct mysql_db *db = (struct mysql_db *)_db;
+	struct mysql_connection *conn;
+	size_t i, size;
+
+	conn = buffer_get_modifyable_data(db->connections, &size);
+	size /= sizeof(*conn);
+	for (i = 0; i < size; i++)
+		(void)driver_mysql_connection_free(&conn[i]);
 
-	mysql_close(db->mysql);
 	pool_unref(db->pool);
 }
 
-static int driver_mysql_do_query(struct mysql_db *db, const char *query)
+static int driver_mysql_connection_do_query(struct mysql_connection *conn,
+					    const char *query)
 {
 	int i;
 
 	for (i = 0; i < 2; i++) {
-		if (!driver_mysql_connect(db))
+		if (!driver_mysql_connect(conn))
 			return 0;
 
-		if (mysql_query(db->mysql, query) == 0)
+		if (mysql_query(conn->mysql, query) == 0)
 			return 1;
 
 		/* failed */
-		switch (mysql_errno(db->mysql)) {
+		switch (mysql_errno(conn->mysql)) {
 		case CR_SERVER_GONE_ERROR:
 		case CR_SERVER_LOST:
 			/* connection lost - try immediate reconnect */
-			db->connected = FALSE;
+			conn->connected = FALSE;
 			break;
 		default:
 			return -1;
@@ -191,21 +272,65 @@
 	return 0;
 }
 
+static int driver_mysql_do_query(struct mysql_db *db, const char *query,
+				 struct mysql_connection **conn_r)
+{
+	struct mysql_connection *conn;
+	size_t size;
+	int reset, ret;
+	unsigned int i, start;
+
+	conn = buffer_get_modifyable_data(db->connections, &size);
+	size /= sizeof(*conn);
+
+	/* go through the connections in round robin. if the connection
+	   isn't available, try next one that is. */
+	start = db->next_query_connection % size;
+	db->next_query_connection++;
+
+	for (reset = 0;; reset++) {
+		i = start;
+		do {
+			ret = driver_mysql_connection_do_query(&conn[i], query);
+			if (ret != 0) {
+				/* success / failure */
+				*conn_r = &conn[i];
+				return ret;
+			}
+
+			/* not connected, try next one */
+			i = (i + 1) % size;
+		} while (i != start);
+
+		if (reset)
+			break;
+
+		/* none are connected. connect_delays may have gotten too high,
+		   reset all of them to see if some are still alive. */
+		i_error("resetting delay");
+		for (i = 0; i < size; i++)
+			conn[i].connect_delay = CONNECT_RESET_DELAY;
+	}
+
+	return 0;
+}
+
 static void driver_mysql_exec(struct sql_db *_db, const char *query)
 {
 	struct mysql_db *db = (struct mysql_db *)_db;
+	struct mysql_connection *conn;
 
-	(void)driver_mysql_do_query(db, query);
+	(void)driver_mysql_do_query(db, query, &conn);
 }
 
 static void driver_mysql_query(struct sql_db *_db, const char *query,
 			       sql_query_callback_t *callback, void *context)
 {
 	struct mysql_db *db = (struct mysql_db *)_db;
-	struct sql_result error_result;
+	struct mysql_connection *conn;
 	struct mysql_result result;
 
-	switch (driver_mysql_do_query(db, query)) {
+	switch (driver_mysql_do_query(db, query, &conn)) {
 	case 0:
 		/* not connected */
 		callback(&sql_not_connected_result, context);
@@ -215,7 +340,9 @@
 		/* query ok */
 		memset(&result, 0, sizeof(result));
 		result.api = driver_mysql_result;
-		result.result = mysql_store_result(db->mysql);
+		result.api.db = _db;
+		result.conn = conn;
+		result.result = mysql_store_result(conn->mysql);
 		if (result.result == NULL)
 			break;
 
@@ -228,21 +355,22 @@
 	}
 
 	/* error */
-	error_result = driver_mysql_error_result;
-	error_result.db = _db;
-	callback(&error_result, context);
+	memset(&result, 0, sizeof(result));
+	result.api = driver_mysql_error_result;
+	result.api.db = _db;
+	result.conn = conn;
+	callback(&result.api, context);
 }
 
 static int driver_mysql_result_next_row(struct sql_result *_result)
 {
 	struct mysql_result *result = (struct mysql_result *)_result;
-	struct mysql_db *db = (struct mysql_db *)_result->db;
 
 	result->row = mysql_fetch_row(result->result);
 	if (result->row != NULL)
 		return 1;
 
-        return mysql_errno(db->mysql) ? -1 : 0;
+	return mysql_errno(result->conn->mysql) ? -1 : 0;
 }
 
 static void driver_mysql_result_fetch_fields(struct mysql_result *result)
@@ -316,11 +444,11 @@
 	return (const char *const *)result->row;
 }
 
-static const char *driver_mysql_result_get_error(struct sql_result *result)
+static const char *driver_mysql_result_get_error(struct sql_result *_result)
 {
-	struct mysql_db *db = (struct mysql_db *)result->db;
+	struct mysql_result *result = (struct mysql_result *)_result;
 
-	return mysql_error(db->mysql);
+	return mysql_error(result->conn->mysql);
 }
 
 struct sql_db driver_mysql_db = {



More information about the dovecot-cvs mailing list