driver-sqlpool.c revision 2d20d46069fa0d4b98790779552dd778d1749d1c
5f5870385cff47efd2f58e7892f251cf13761528Timo Sirainen/* Copyright (c) 2010-2011 Dovecot authors, see the included COPYING file */
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainen
08d6658a4e2ec8104cd1307f6baa75fdb07a24f8Mark Washenberger#include "lib.h"
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainen#include "array.h"
1b04762685272a53643ac2179939537a44c7c044Timo Sirainen#include "llist.h"
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen#include "ioloop.h"
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen#include "sql-api-private.h"
1d3b9fce06b466bcf64f9ab7b622f3a6e4e939baTimo Sirainen
1b04762685272a53643ac2179939537a44c7c044Timo Sirainen#include <time.h>
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen#define QUERY_TIMEOUT_SECS 6
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainen
1b04762685272a53643ac2179939537a44c7c044Timo Sirainenstruct sqlpool_host {
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen char *connect_string;
a9ba65037107e370c126d2b7e8c6e3f2a4aafd23Timo Sirainen
00f5efa3156ab6a0b4f21e8c703d0eb816cf3091Timo Sirainen unsigned int connection_count;
01758d8aaa05940397c8210af52d7f45a5b676aeTimo Sirainen};
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen
1b04762685272a53643ac2179939537a44c7c044Timo Sirainenstruct sqlpool_connection {
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainen struct sql_db *db;
4dbe08e1f7f1271299ada9338ff5015367efd0b7Timo Sirainen unsigned int host_idx;
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainen};
4dbe08e1f7f1271299ada9338ff5015367efd0b7Timo Sirainen
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainenstruct sqlpool_db {
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainen struct sql_db api;
d482079eb385cd071bbc9637cacee225e4aff968Timo Sirainen
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen pool_t pool;
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen const struct sql_db *driver;
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen unsigned int connection_limit;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainen ARRAY_DEFINE(hosts, struct sqlpool_host);
6f2e601fa36133320aa88258106be46a175a0e53Timo Sirainen /* all connections from all hosts */
6f2e601fa36133320aa88258106be46a175a0e53Timo Sirainen ARRAY_DEFINE(all_connections, struct sqlpool_connection);
6f2e601fa36133320aa88258106be46a175a0e53Timo Sirainen /* index of last connection in all_connections that was used to
6f2e601fa36133320aa88258106be46a175a0e53Timo Sirainen send a query. */
6f2e601fa36133320aa88258106be46a175a0e53Timo Sirainen unsigned int last_query_conn_idx;
6f2e601fa36133320aa88258106be46a175a0e53Timo Sirainen
6f2e601fa36133320aa88258106be46a175a0e53Timo Sirainen /* queued requests */
6f2e601fa36133320aa88258106be46a175a0e53Timo Sirainen struct sqlpool_request *requests_head, *requests_tail;
6f2e601fa36133320aa88258106be46a175a0e53Timo Sirainen struct timeout *request_to;
6f2e601fa36133320aa88258106be46a175a0e53Timo Sirainen};
8f1d14e3ada93a6d6ee64f73c6e6ae2364d8eba1Timo Sirainen
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainenstruct sqlpool_request {
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen struct sqlpool_request *prev, *next;
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen struct sqlpool_db *db;
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen time_t created;
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen unsigned int host_idx;
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen unsigned int retried:1;
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen /* requests are a) queries */
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen char *query;
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen sql_query_callback_t *callback;
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen void *context;
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen /* b) transaction waiters */
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen struct sqlpool_transaction_context *trans;
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen};
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainenstruct sqlpool_transaction_context {
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen struct sql_transaction_context ctx;
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen sql_commit_callback_t *callback;
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen void *context;
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen pool_t query_pool;
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen struct sqlpool_request *commit_request;
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen};
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainenextern struct sql_db driver_sqlpool_db;
86791365b10f45982c88e70f2eb94fd6c3fea151Timo Sirainen
86791365b10f45982c88e70f2eb94fd6c3fea151Timo Sirainenstatic struct sqlpool_connection *
86791365b10f45982c88e70f2eb94fd6c3fea151Timo Sirainensqlpool_add_connection(struct sqlpool_db *db, struct sqlpool_host *host,
86791365b10f45982c88e70f2eb94fd6c3fea151Timo Sirainen unsigned int host_idx);
86791365b10f45982c88e70f2eb94fd6c3fea151Timo Sirainenstatic void
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainendriver_sqlpool_query_callback(struct sql_result *result,
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainen struct sqlpool_request *request);
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainenstatic void
f9ef36afc131626754716d6f4874a2ad04da0ac4Timo Sirainendriver_sqlpool_commit_callback(const char *error,
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen struct sqlpool_transaction_context *ctx);
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainenstatic struct sqlpool_request *
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainensqlpool_request_new(struct sqlpool_db *db, const char *query)
cd94aeaa294f7cc507206b4b2075852f00e14d61Timo Sirainen{
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen struct sqlpool_request *request;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen request = i_new(struct sqlpool_request, 1);
7753eaa6a4275e074b4ce8428b85d9d04fc67f31Timo Sirainen request->db = db;
7753eaa6a4275e074b4ce8428b85d9d04fc67f31Timo Sirainen request->created = time(NULL);
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen request->query = i_strdup(query);
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen return request;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen}
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainen
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainenstatic void
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainensqlpool_request_free(struct sqlpool_request **_request)
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen{
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen struct sqlpool_request *request = *_request;
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainen *_request = NULL;
01758d8aaa05940397c8210af52d7f45a5b676aeTimo Sirainen
01758d8aaa05940397c8210af52d7f45a5b676aeTimo Sirainen i_assert(request->prev == NULL && request->next == NULL);
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen i_free(request->query);
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen i_free(request);
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen}
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainenstatic void
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainensqlpool_request_abort(struct sqlpool_request **_request)
01758d8aaa05940397c8210af52d7f45a5b676aeTimo Sirainen{
01758d8aaa05940397c8210af52d7f45a5b676aeTimo Sirainen struct sqlpool_request *request = *_request;
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen
01758d8aaa05940397c8210af52d7f45a5b676aeTimo Sirainen *_request = NULL;
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen if (request->callback != NULL)
86791365b10f45982c88e70f2eb94fd6c3fea151Timo Sirainen request->callback(&sql_not_connected_result, request->context);
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen i_assert(request->prev != NULL ||
8846e6eed6177a39b662f4f1ebf9b84ad1f0b7ecTimo Sirainen request->db->requests_head == request);
b55f914c0ade77252cfd798ea8eb9a84bda56315Timo Sirainen DLLIST2_REMOVE(&request->db->requests_head,
b55f914c0ade77252cfd798ea8eb9a84bda56315Timo Sirainen &request->db->requests_tail, request);
a1f9314bdd7da1a8f5596b49ec6b7b5c5daa4079Timo Sirainen sqlpool_request_free(&request);
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen}
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainenstatic struct sql_transaction_context *
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainendriver_sqlpool_new_conn_trans(struct sqlpool_transaction_context *trans,
d176f84ce5ca2073f4dfbafb457b9c74f6bf0d76Timo Sirainen struct sql_db *conndb)
124e615e2949883473e30950a15a563feef26406Timo Sirainen{
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainen struct sql_transaction_context *conn_trans;
d1fff80640050631b06bfab904a34b2ad24601e8Timo Sirainen
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen conn_trans = sql_transaction_begin(conndb);
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen /* backend will use our queries list (we might still append more
d1fff80640050631b06bfab904a34b2ad24601e8Timo Sirainen queries to the list) */
d1fff80640050631b06bfab904a34b2ad24601e8Timo Sirainen conn_trans->head = trans->ctx.head;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen conn_trans->tail = trans->ctx.tail;
124e615e2949883473e30950a15a563feef26406Timo Sirainen return conn_trans;
cef2be5fb553b05f421f86c1ef497f0dc29d069eTimo Sirainen}
cef2be5fb553b05f421f86c1ef497f0dc29d069eTimo Sirainen
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainenstatic void
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainensqlpool_request_handle_transaction(struct sql_db *conndb,
d1fff80640050631b06bfab904a34b2ad24601e8Timo Sirainen struct sqlpool_transaction_context *trans)
e34d170f8f0e084bd94bfbc1a7085ece67e508dfTimo Sirainen{
d1fff80640050631b06bfab904a34b2ad24601e8Timo Sirainen struct sql_transaction_context *conn_trans;
e34d170f8f0e084bd94bfbc1a7085ece67e508dfTimo Sirainen
1b04762685272a53643ac2179939537a44c7c044Timo Sirainen sqlpool_request_free(&trans->commit_request);
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen conn_trans = driver_sqlpool_new_conn_trans(trans, conndb);
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen sql_transaction_commit(&conn_trans,
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen driver_sqlpool_commit_callback, trans);
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainen}
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainenstatic void
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainensqlpool_request_send_next(struct sqlpool_db *db, struct sql_db *conndb)
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen{
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen struct sqlpool_request *request;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen if (db->requests_head == NULL || !SQL_DB_IS_READY(conndb))
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen return;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen request = db->requests_head;
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen DLLIST2_REMOVE(&db->requests_head, &db->requests_tail, request);
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen timeout_reset(db->request_to);
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen if (request->query != NULL) {
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen sql_query(conndb, request->query,
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen driver_sqlpool_query_callback, request);
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen } else if (request->trans != NULL) {
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen sqlpool_request_handle_transaction(conndb, request->trans);
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen } else {
b55f914c0ade77252cfd798ea8eb9a84bda56315Timo Sirainen i_unreached();
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen }
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen}
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainenstatic void sqlpool_reconnect(struct sql_db *conndb)
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen{
4307c886579381dbb1897ea1388ae6978c96f560Timo Sirainen timeout_remove(&conndb->to_reconnect);
4307c886579381dbb1897ea1388ae6978c96f560Timo Sirainen (void)sql_connect(conndb);
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen}
4307c886579381dbb1897ea1388ae6978c96f560Timo Sirainen
6667f4006484bcaf9ab8fd03b97a7a3ae84ce0d5Timo Sirainenstatic struct sqlpool_host *
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainensqlpool_find_host_with_least_connections(struct sqlpool_db *db,
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen unsigned int *host_idx_r)
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen{
3b22894b8805b186c73d8b754001e8d7e944be85Timo Sirainen struct sqlpool_host *hosts, *min = NULL;
3b22894b8805b186c73d8b754001e8d7e944be85Timo Sirainen unsigned int i, count;
3b22894b8805b186c73d8b754001e8d7e944be85Timo Sirainen
3b22894b8805b186c73d8b754001e8d7e944be85Timo Sirainen hosts = array_get_modifiable(&db->hosts, &count);
3b22894b8805b186c73d8b754001e8d7e944be85Timo Sirainen i_assert(count > 0);
3b22894b8805b186c73d8b754001e8d7e944be85Timo Sirainen
747c5d36868aa738b64ceedc87cda169aa1dbe96Timo Sirainen min = &hosts[0];
747c5d36868aa738b64ceedc87cda169aa1dbe96Timo Sirainen *host_idx_r = 0;
747c5d36868aa738b64ceedc87cda169aa1dbe96Timo Sirainen
3b22894b8805b186c73d8b754001e8d7e944be85Timo Sirainen for (i = 1; i < count; i++) {
3b22894b8805b186c73d8b754001e8d7e944be85Timo Sirainen if (min->connection_count > hosts[i].connection_count) {
db693bf6fcae96d834567f1782257517b7207655Timo Sirainen min = &hosts[i];
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen *host_idx_r = i;
6469403946bdf147886daba5ee8de07516c32238Pascal Volk }
6469403946bdf147886daba5ee8de07516c32238Pascal Volk }
6469403946bdf147886daba5ee8de07516c32238Pascal Volk return min;
6469403946bdf147886daba5ee8de07516c32238Pascal Volk}
6469403946bdf147886daba5ee8de07516c32238Pascal Volk
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainenstatic bool sqlpool_have_successful_connections(struct sqlpool_db *db)
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen{
a3fe8c0c54d87822f4b4f8f0d10caac611861b2bTimo Sirainen const struct sqlpool_connection *conn;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen array_foreach(&db->all_connections, conn) {
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen if (conn->db->state >= SQL_DB_STATE_IDLE)
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen return TRUE;
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen }
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen return FALSE;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen}
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainenstatic void
124e615e2949883473e30950a15a563feef26406Timo Sirainensqlpool_handle_connect_failed(struct sqlpool_db *db, struct sql_db *conndb)
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen{
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen struct sqlpool_host *host;
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen unsigned int host_idx;
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen if (conndb->connect_failure_count > 0) {
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen /* increase delay between reconnections to this
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen server */
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen conndb->connect_delay *= 5;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen if (conndb->connect_delay > SQL_CONNECT_MAX_DELAY)
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen conndb->connect_delay = SQL_CONNECT_MAX_DELAY;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen }
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen conndb->connect_failure_count++;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen /* reconnect after the delay */
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen if (conndb->to_reconnect != NULL)
3ba9a079592f46e94ce846e5aa80e4d479cd5e41Timo Sirainen timeout_remove(&conndb->to_reconnect);
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen conndb->to_reconnect = timeout_add(conndb->connect_delay * 1000,
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen sqlpool_reconnect, conndb);
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen /* if we have zero successful hosts and there still are hosts
578ef2538ccf42e2a48234c24a8b709397101d88Timo Sirainen without connections, connect to one of them. */
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen if (!sqlpool_have_successful_connections(db)) {
124e615e2949883473e30950a15a563feef26406Timo Sirainen host = sqlpool_find_host_with_least_connections(db, &host_idx);
124e615e2949883473e30950a15a563feef26406Timo Sirainen if (host->connection_count == 0)
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen (void)sqlpool_add_connection(db, host, host_idx);
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen }
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen}
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainenstatic void
124e615e2949883473e30950a15a563feef26406Timo Sirainensqlpool_state_changed(struct sql_db *conndb, enum sql_db_state prev_state,
124e615e2949883473e30950a15a563feef26406Timo Sirainen void *context)
124e615e2949883473e30950a15a563feef26406Timo Sirainen{
124e615e2949883473e30950a15a563feef26406Timo Sirainen struct sqlpool_db *db = context;
124e615e2949883473e30950a15a563feef26406Timo Sirainen
124e615e2949883473e30950a15a563feef26406Timo Sirainen if (conndb->state == SQL_DB_STATE_IDLE) {
124e615e2949883473e30950a15a563feef26406Timo Sirainen conndb->connect_failure_count = 0;
124e615e2949883473e30950a15a563feef26406Timo Sirainen conndb->connect_delay = SQL_CONNECT_MIN_DELAY;
124e615e2949883473e30950a15a563feef26406Timo Sirainen sqlpool_request_send_next(db, conndb);
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen }
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen if (prev_state == SQL_DB_STATE_CONNECTING &&
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen conndb->state == SQL_DB_STATE_DISCONNECTED &&
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen !conndb->no_reconnect)
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen sqlpool_handle_connect_failed(db, conndb);
1d3b9fce06b466bcf64f9ab7b622f3a6e4e939baTimo Sirainen}
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen
86791365b10f45982c88e70f2eb94fd6c3fea151Timo Sirainenstatic struct sqlpool_connection *
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainensqlpool_add_connection(struct sqlpool_db *db, struct sqlpool_host *host,
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen unsigned int host_idx)
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen{
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen struct sql_db *conndb;
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen struct sqlpool_connection *conn;
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen host->connection_count++;
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen
573731df7d9b2ebb9028311a6c33b338dd2dd34dTimo Sirainen conndb = db->driver->v.init(host->connect_string);
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen i_array_init(&conndb->module_contexts, 5);
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen
124e615e2949883473e30950a15a563feef26406Timo Sirainen conndb->state_change_callback = sqlpool_state_changed;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen conndb->state_change_context = db;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen conndb->connect_delay = SQL_CONNECT_MIN_DELAY;
53dff078a5f49e9d28d6c81d3437755e27526e3eTimo Sirainen
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen conn = array_append_space(&db->all_connections);
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen conn->host_idx = host_idx;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen conn->db = conndb;
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen return conn;
4d4d6d4745682790c20d759ba93dbea46b812c5dTimo Sirainen}
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainenstatic struct sqlpool_connection *
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainensqlpool_add_new_connection(struct sqlpool_db *db)
cf0ad1a0bddb0787f3d7b408a96d721a8b2a98a3Timo Sirainen{
4da8c6cdefabd31262318c32da3c13de1d9ea953Timo Sirainen struct sqlpool_host *host;
4d4d6d4745682790c20d759ba93dbea46b812c5dTimo Sirainen unsigned int host_idx;
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainen
548f87789cf9865572b7b86f7be5a9bbfa132f3fTimo Sirainen host = sqlpool_find_host_with_least_connections(db, &host_idx);
if (host->connection_count >= db->connection_limit)
return NULL;
else
return sqlpool_add_connection(db, host, host_idx);
}
static const struct sqlpool_connection *
sqlpool_find_available_connection(struct sqlpool_db *db,
unsigned int unwanted_host_idx,
bool *all_disconnected_r)
{
const struct sqlpool_connection *conns;
unsigned int i, count;
*all_disconnected_r = TRUE;
conns = array_get(&db->all_connections, &count);
for (i = 0; i < count; i++) {
unsigned int idx = (i + db->last_query_conn_idx) % count;
struct sql_db *conndb = conns[idx].db;
if (conns[idx].host_idx == unwanted_host_idx)
continue;
if (!SQL_DB_IS_READY(conndb)) {
/* see if we could reconnect to it immediately */
(void)sql_connect(conndb);
}
if (SQL_DB_IS_READY(conndb)) {
db->last_query_conn_idx = idx;
*all_disconnected_r = FALSE;
return &conns[idx];
}
if (conndb->state != SQL_DB_STATE_DISCONNECTED)
*all_disconnected_r = FALSE;
}
return NULL;
}
static bool
driver_sqlpool_get_connection(struct sqlpool_db *db,
unsigned int unwanted_host_idx,
const struct sqlpool_connection **conn_r)
{
const struct sqlpool_connection *conn, *conns;
unsigned int i, count;
bool all_disconnected;
conn = sqlpool_find_available_connection(db, unwanted_host_idx,
&all_disconnected);
if (conn == NULL && unwanted_host_idx != -1U) {
/* maybe there are no wanted hosts. use any of them. */
conn = sqlpool_find_available_connection(db, -1U,
&all_disconnected);
}
if (conn == NULL && all_disconnected) {
/* no connected connections. connect_delays may have gotten too
high, reset all of them to see if some are still alive. */
conns = array_get(&db->all_connections, &count);
for (i = 0; i < count; i++) {
struct sql_db *conndb = conns[i].db;
if (conndb->connect_delay > SQL_CONNECT_RESET_DELAY)
conndb->connect_delay = SQL_CONNECT_RESET_DELAY;
}
conn = sqlpool_find_available_connection(db, -1U,
&all_disconnected);
}
if (conn == NULL) {
/* still nothing. try creating new connections */
conn = sqlpool_add_new_connection(db);
if (conn != NULL)
(void)sql_connect(conn->db);
if (conn == NULL || !SQL_DB_IS_READY(conn->db))
return FALSE;
}
*conn_r = conn;
return TRUE;
}
static bool
driver_sqlpool_get_sync_connection(struct sqlpool_db *db,
const struct sqlpool_connection **conn_r)
{
const struct sqlpool_connection *conns;
unsigned int i, count;
if (driver_sqlpool_get_connection(db, -1U, conn_r))
return TRUE;
/* no idling connections, but maybe we can find one that's trying to
connect to server, and we can use it once it's finished */
conns = array_get(&db->all_connections, &count);
for (i = 0; i < count; i++) {
if (conns[i].db->state == SQL_DB_STATE_CONNECTING) {
*conn_r = &conns[i];
return TRUE;
}
}
return FALSE;
}
static void
driver_sqlpool_parse_hosts(struct sqlpool_db *db, const char *connect_string)
{
const char *const *args, *key, *value, *const *hostnamep;
struct sqlpool_host *host;
ARRAY_TYPE(const_string) hostnames, connect_args;
t_array_init(&hostnames, 8);
t_array_init(&connect_args, 32);
/* connect string is a space separated list. it may contain
backend-specific strings which we'll pass as-is. we'll only care
about our own settings, plus the host settings. */
args = t_strsplit_spaces(connect_string, " ");
for (; *args != NULL; args++) {
value = strchr(*args, '=');
if (value == NULL) {
key = *args;
value = "";
} else {
key = t_strdup_until(*args, value);
value++;
}
if (strcmp(key, "maxconns") == 0) {
if (str_to_uint(value, &db->connection_limit) < 0) {
i_fatal("Invalid value for maxconns: %s",
value);
}
} else if (strcmp(key, "host") == 0) {
array_append(&hostnames, &value, 1);
} else {
array_append(&connect_args, args, 1);
}
}
/* build a new connect string without our settings or hosts */
(void)array_append_space(&connect_args);
connect_string = t_strarray_join(array_idx(&connect_args, 0), " ");
if (array_count(&hostnames) == 0) {
/* no hosts specified. create a default one. */
host = array_append_space(&db->hosts);
host->connect_string = i_strdup(connect_string);
} else {
if (*connect_string == '\0')
connect_string = NULL;
array_foreach(&hostnames, hostnamep) {
host = array_append_space(&db->hosts);
host->connect_string =
i_strconcat("host=", *hostnamep, " ",
connect_string, NULL);
}
}
if (db->connection_limit == 0)
db->connection_limit = SQL_DEFAULT_CONNECTION_LIMIT;
}
struct sql_db *
driver_sqlpool_init(const char *connect_string, const struct sql_db *driver)
{
struct sqlpool_db *db;
i_assert(connect_string != NULL);
db = i_new(struct sqlpool_db, 1);
db->driver = driver;
db->api = driver_sqlpool_db;
db->api.flags = driver->flags;
i_array_init(&db->hosts, 8);
T_BEGIN {
driver_sqlpool_parse_hosts(db, connect_string);
} T_END;
i_array_init(&db->all_connections, 16);
/* always have at least one backend connection initialized */
(void)sqlpool_add_new_connection(db);
return &db->api;
}
static void driver_sqlpool_abort_requests(struct sqlpool_db *db)
{
while (db->requests_head != NULL) {
struct sqlpool_request *request = db->requests_head;
sqlpool_request_abort(&request);
}
if (db->request_to != NULL)
timeout_remove(&db->request_to);
}
static void driver_sqlpool_deinit(struct sql_db *_db)
{
struct sqlpool_db *db = (struct sqlpool_db *)_db;
struct sqlpool_host *host;
struct sqlpool_connection *conn;
array_foreach_modifiable(&db->all_connections, conn)
sql_deinit(&conn->db);
array_clear(&db->all_connections);
driver_sqlpool_abort_requests(db);
array_foreach_modifiable(&db->hosts, host)
i_free(host->connect_string);
i_assert(array_count(&db->all_connections) == 0);
array_free(&db->hosts);
array_free(&db->all_connections);
array_free(&_db->module_contexts);
i_free(db);
}
static int driver_sqlpool_connect(struct sql_db *_db)
{
struct sqlpool_db *db = (struct sqlpool_db *)_db;
const struct sqlpool_connection *conn;
int ret = -1, ret2;
array_foreach(&db->all_connections, conn) {
ret2 = sql_connect(conn->db);
if (ret2 > 0)
return 1;
if (ret2 == 0)
ret = 0;
}
return ret;
}
static void driver_sqlpool_disconnect(struct sql_db *_db)
{
struct sqlpool_db *db = (struct sqlpool_db *)_db;
const struct sqlpool_connection *conn;
array_foreach(&db->all_connections, conn)
sql_disconnect(conn->db);
driver_sqlpool_abort_requests(db);
}
static const char *
driver_sqlpool_escape_string(struct sql_db *_db, const char *string)
{
struct sqlpool_db *db = (struct sqlpool_db *)_db;
const struct sqlpool_connection *conns;
unsigned int i, count;
/* use the first ready connection */
conns = array_get(&db->all_connections, &count);
for (i = 0; i < count; i++) {
if (SQL_DB_IS_READY(conns[i].db))
return sql_escape_string(conns[i].db, string);
}
/* no ready connections. just use the first one (we're guaranteed
to always have one) */
return sql_escape_string(conns[0].db, string);
}
static void driver_sqlpool_timeout(struct sqlpool_db *db)
{
while (db->requests_head != NULL) {
struct sqlpool_request *request = db->requests_head;
if (request->created + SQL_QUERY_TIMEOUT_SECS > ioloop_time)
break;
i_error("%s: Query timed out "
"(no free connections for %u secs): %s",
db->driver->name,
(unsigned int)(ioloop_time - request->created),
request->query != NULL ? request->query :
"<transaction>");
sqlpool_request_abort(&request);
}
if (db->requests_head == NULL)
timeout_remove(&db->request_to);
}
static void
driver_sqlpool_prepend_request(struct sqlpool_db *db,
struct sqlpool_request *request)
{
DLLIST2_PREPEND(&db->requests_head, &db->requests_tail, request);
if (db->request_to == NULL) {
db->request_to = timeout_add(SQL_QUERY_TIMEOUT_SECS * 1000,
driver_sqlpool_timeout, db);
}
}
static void
driver_sqlpool_append_request(struct sqlpool_db *db,
struct sqlpool_request *request)
{
DLLIST2_APPEND(&db->requests_head, &db->requests_tail, request);
if (db->request_to == NULL) {
db->request_to = timeout_add(SQL_QUERY_TIMEOUT_SECS * 1000,
driver_sqlpool_timeout, db);
}
}
static void
driver_sqlpool_query_callback(struct sql_result *result,
struct sqlpool_request *request)
{
struct sqlpool_db *db = request->db;
const struct sqlpool_connection *conn;
struct sql_db *conndb;
if (result->failed_try_retry && !request->retried) {
i_error("%s: Query failed, retrying: %s",
db->driver->name, sql_result_get_error(result));
request->retried = TRUE;
driver_sqlpool_prepend_request(db, request);
if (driver_sqlpool_get_connection(request->db,
request->host_idx, &conn)) {
request->host_idx = conn->host_idx;
sqlpool_request_send_next(db, conn->db);
}
} else {
if (result->failed) {
i_error("%s: Query failed, aborting: %s",
db->driver->name, request->query);
}
conndb = result->db;
if (request->callback != NULL)
request->callback(result, request->context);
sqlpool_request_free(&request);
sqlpool_request_send_next(db, conndb);
}
}
static void driver_sqlpool_query(struct sql_db *_db, const char *query,
sql_query_callback_t *callback, void *context)
{
struct sqlpool_db *db = (struct sqlpool_db *)_db;
struct sqlpool_request *request;
const struct sqlpool_connection *conn;
request = sqlpool_request_new(db, query);
request->callback = callback;
request->context = context;
if (!driver_sqlpool_get_connection(db, -1U, &conn))
driver_sqlpool_append_request(db, request);
else {
request->host_idx = conn->host_idx;
sql_query(conn->db, query, driver_sqlpool_query_callback,
request);
}
}
static void driver_sqlpool_exec(struct sql_db *_db, const char *query)
{
driver_sqlpool_query(_db, query, NULL, NULL);
}
static struct sql_result *
driver_sqlpool_query_s(struct sql_db *_db, const char *query)
{
struct sqlpool_db *db = (struct sqlpool_db *)_db;
const struct sqlpool_connection *conn;
struct sql_result *result;
if (!driver_sqlpool_get_sync_connection(db, &conn)) {
sql_not_connected_result.refcount++;
return &sql_not_connected_result;
}
result = sql_query_s(conn->db, query);
if (result->failed_try_retry) {
if (!driver_sqlpool_get_sync_connection(db, &conn))
return result;
sql_result_unref(result);
result = sql_query_s(conn->db, query);
}
return result;
}
static struct sql_transaction_context *
driver_sqlpool_transaction_begin(struct sql_db *_db)
{
struct sqlpool_transaction_context *ctx;
ctx = i_new(struct sqlpool_transaction_context, 1);
ctx->ctx.db = _db;
/* queue changes until commit. even if we did have a free connection
now, don't use it or multiple open transactions could tie up all
connections. */
ctx->query_pool = pool_alloconly_create("sqlpool transaction", 1024);
return &ctx->ctx;
}
static void
driver_sqlpool_transaction_free(struct sqlpool_transaction_context *ctx)
{
if (ctx->commit_request != NULL)
sqlpool_request_abort(&ctx->commit_request);
if (ctx->query_pool != NULL)
pool_unref(&ctx->query_pool);
i_free(ctx);
}
static void
driver_sqlpool_commit_callback(const char *error,
struct sqlpool_transaction_context *ctx)
{
ctx->callback(error, ctx->context);
driver_sqlpool_transaction_free(ctx);
}
static void
driver_sqlpool_transaction_commit(struct sql_transaction_context *_ctx,
sql_commit_callback_t *callback,
void *context)
{
struct sqlpool_transaction_context *ctx =
(struct sqlpool_transaction_context *)_ctx;
struct sqlpool_db *db = (struct sqlpool_db *)_ctx->db;
const struct sqlpool_connection *conn;
ctx->callback = callback;
ctx->context = context;
ctx->commit_request = sqlpool_request_new(db, NULL);
ctx->commit_request->trans = ctx;
if (driver_sqlpool_get_connection(db, -1U, &conn))
sqlpool_request_handle_transaction(conn->db, ctx);
else
driver_sqlpool_append_request(db, ctx->commit_request);
}
static int
driver_sqlpool_transaction_commit_s(struct sql_transaction_context *_ctx,
const char **error_r)
{
struct sqlpool_transaction_context *ctx =
(struct sqlpool_transaction_context *)_ctx;
struct sqlpool_db *db = (struct sqlpool_db *)_ctx->db;
const struct sqlpool_connection *conn;
struct sql_transaction_context *conn_trans;
int ret;
*error_r = NULL;
if (!driver_sqlpool_get_sync_connection(db, &conn)) {
*error_r = SQL_ERRSTR_NOT_CONNECTED;
driver_sqlpool_transaction_free(ctx);
return -1;
}
conn_trans = driver_sqlpool_new_conn_trans(ctx, conn->db);
ret = sql_transaction_commit_s(&conn_trans, error_r);
driver_sqlpool_transaction_free(ctx);
return ret;
}
static void
driver_sqlpool_transaction_rollback(struct sql_transaction_context *_ctx)
{
struct sqlpool_transaction_context *ctx =
(struct sqlpool_transaction_context *)_ctx;
driver_sqlpool_transaction_free(ctx);
}
static void
driver_sqlpool_update(struct sql_transaction_context *_ctx, const char *query,
unsigned int *affected_rows)
{
struct sqlpool_transaction_context *ctx =
(struct sqlpool_transaction_context *)_ctx;
/* we didn't get a connection for transaction immediately.
queue updates until commit transfers all of these */
sql_transaction_add_query(&ctx->ctx, ctx->query_pool,
query, affected_rows);
}
struct sql_db driver_sqlpool_db = {
"",
.v = {
NULL,
driver_sqlpool_deinit,
driver_sqlpool_connect,
driver_sqlpool_disconnect,
driver_sqlpool_escape_string,
driver_sqlpool_exec,
driver_sqlpool_query,
driver_sqlpool_query_s,
driver_sqlpool_transaction_begin,
driver_sqlpool_transaction_commit,
driver_sqlpool_transaction_commit_s,
driver_sqlpool_transaction_rollback,
driver_sqlpool_update
}
};