From 0918b03678c12090002acde9cac5103e9287c350 Mon Sep 17 00:00:00 2001 From: Geert Janssens Date: Sun, 21 Feb 2010 23:10:54 +0000 Subject: [PATCH] Bug #608032 - MySQL timeout and no attempt reconnect, second version This version builds on Phil's implementation of the dbi error callback functions to test for a timeout and to do the reconnect. The same error handling is equally implemented for postgres and sqlite. Unlike MySQL these two database types don't actually generate timeouts, but the functionality can be used later on for other error types as well. git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@18706 57a11ea4-9604-0410-9ed3-97b8803252fd --- src/backend/dbi/gnc-backend-dbi.c | 88 ++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 14 deletions(-) diff --git a/src/backend/dbi/gnc-backend-dbi.c b/src/backend/dbi/gnc-backend-dbi.c index 0879256abd..dc7b420ada 100644 --- a/src/backend/dbi/gnc-backend-dbi.c +++ b/src/backend/dbi/gnc-backend-dbi.c @@ -149,8 +149,38 @@ typedef struct dbi_conn conn; /*@ observer @*/ provider_functions_t* provider; + gint last_error; // Code of the last error that occurred. This is set in the error callback function + gint error_repeat; // Used in case of transient errors. After such error, another attempt at the + // original call is allowed. error_repeat tracks the number of attempts and can + // be used to prevent infinite loops. + gboolean retry; // Signals the calling function that it should retry (the error handler detected + // transient error and managed to resolve it, but it can't run the original query) } GncDbiSqlConnection; +#define DBI_MAX_CONN_ATTEMPTS 5 + +/* ================================================================= */ + +static void +gnc_dbi_set_error( GncDbiSqlConnection* dbi_conn, gint last_error, + gint error_repeat, gboolean retry ) +{ + g_return_if_fail( dbi_conn != NULL ); + + dbi_conn->last_error = last_error; + if ( error_repeat > 0 ) + dbi_conn->error_repeat = dbi_conn->error_repeat + error_repeat; + else + dbi_conn->error_repeat = 0; + dbi_conn->retry = retry; +} + +static void +gnc_dbi_init_error( GncDbiSqlConnection* dbi_conn ) +{ + gnc_dbi_set_error( dbi_conn, ERR_BACKEND_NO_ERR, 0, FALSE ); +} + /* ================================================================= */ static void @@ -171,11 +201,13 @@ create_tables_cb( const gchar* type, gpointer data_p, gpointer be_p ) static void sqlite3_error_fn( dbi_conn conn, /*@ unused @*/ void* user_data ) { - //GncDbiBackend *be = (GncDbiBackend*)user_data; + GncDbiBackend *be = (GncDbiBackend*)user_data; + GncDbiSqlConnection *dbi_conn = (GncDbiSqlConnection*)be->sql_be.conn; const gchar* msg; (void)dbi_conn_error( conn, &msg ); PERR( "DBI error: %s\n", msg ); + gnc_dbi_set_error( dbi_conn, ERR_BACKEND_MISC, 0, FALSE ); } static void @@ -278,17 +310,35 @@ static void mysql_error_fn( dbi_conn conn, void* user_data ) { GncDbiBackend *be = (GncDbiBackend*)user_data; + GncDbiSqlConnection *dbi_conn = (GncDbiSqlConnection*)be->sql_be.conn; const gchar* msg; + gint err_num; - (void)dbi_conn_error( conn, &msg ); - if ( g_str_has_prefix( msg, "1049: Unknown database" ) ) + err_num = dbi_conn_error( conn, &msg ); + if ( err_num == 1049 ) // Database doesn't exist { PINFO( "DBI error: %s\n", msg ); be->exists = FALSE; + gnc_dbi_set_error( dbi_conn, ERR_BACKEND_NO_SUCH_DB, 0, FALSE ); } - else + else if ( err_num == 2006 ) // Server has gone away + { + if (dbi_conn->error_repeat > DBI_MAX_CONN_ATTEMPTS ) + { + PERR( "DBI error: %s - Failed to reconnect after %d attempts.\n", msg, DBI_MAX_CONN_ATTEMPTS ); + gnc_dbi_set_error( dbi_conn, ERR_BACKEND_CANT_CONNECT, 0, FALSE ); + } else + { + PINFO( "DBI error: %s - Reconnecting...\n", msg ); + gnc_dbi_set_error( dbi_conn, ERR_BACKEND_CONN_LOST, 1, TRUE ); + + (void)dbi_conn_connect( conn ); + } + } + else // Any other error { PERR( "DBI error: %s\n", msg ); + gnc_dbi_set_error( dbi_conn, ERR_BACKEND_MISC, 0, FALSE ); } } @@ -511,6 +561,7 @@ static void pgsql_error_fn( dbi_conn conn, void* user_data ) { GncDbiBackend *be = (GncDbiBackend*)user_data; + GncDbiSqlConnection *dbi_conn = (GncDbiSqlConnection*)be->sql_be.conn; const gchar* msg; (void)dbi_conn_error( conn, &msg ); @@ -519,10 +570,12 @@ pgsql_error_fn( dbi_conn conn, void* user_data ) { PINFO( "DBI error: %s\n", msg ); be->exists = FALSE; + gnc_dbi_set_error( dbi_conn, ERR_BACKEND_NO_SUCH_DB, 0, FALSE ); } else { PERR( "DBI error: %s\n", msg ); + gnc_dbi_set_error( dbi_conn, ERR_BACKEND_MISC, 0, FALSE ); } } @@ -802,7 +855,10 @@ gnc_dbi_sync_all( QofBackend* qbe, /*@ dependent @*/ QofBook *book ) const gchar* table_name = (const gchar*)node->data; dbi_result result; - result = dbi_conn_queryf( be->conn, "DROP TABLE %s", table_name ); + do { + gnc_dbi_init_error( ((GncDbiSqlConnection*)(be->sql_be.conn)) ); + result = dbi_conn_queryf( be->conn, "DROP TABLE %s", table_name ); + } while ( ((GncDbiSqlConnection*)(be->sql_be.conn))->retry ); if ( result != NULL ) { status = dbi_result_free( result ); @@ -1416,7 +1472,10 @@ conn_execute_select_statement( GncSqlConnection* conn, GncSqlStatement* stmt ) dbi_result result; DEBUG( "SQL: %s\n", dbi_stmt->sql->str ); - result = dbi_conn_query( dbi_conn->conn, dbi_stmt->sql->str ); + do { + gnc_dbi_init_error( dbi_conn ); + result = dbi_conn_query( dbi_conn->conn, dbi_stmt->sql->str ); + } while ( dbi_conn->retry ); if ( result == NULL ) { PERR( "Error executing SQL %s\n", dbi_stmt->sql->str ); @@ -1435,7 +1494,10 @@ conn_execute_nonselect_statement( GncSqlConnection* conn, GncSqlStatement* stmt gint status; DEBUG( "SQL: %s\n", dbi_stmt->sql->str ); - result = dbi_conn_query( dbi_conn->conn, dbi_stmt->sql->str ); + do { + gnc_dbi_init_error( dbi_conn ); + result = dbi_conn_query( dbi_conn->conn, dbi_stmt->sql->str ); + } while( dbi_conn->retry ); if ( result == NULL ) { PERR( "Error executing SQL %s\n", dbi_stmt->sql->str ); @@ -1513,15 +1575,11 @@ conn_begin_transaction( /*@ unused @*/ GncSqlConnection* conn ) gint status; DEBUG( "BEGIN\n" ); - result = dbi_conn_queryf( dbi_conn->conn, "BEGIN" ); - /* Handle MySQL connection timeouts with reconnect */ - if (result == NULL && dbi_conn_error( dbi_conn->conn, NULL ) == 2006 ) - { - DEBUG( "MySQL server has gone away, reconnecting and retrying...\n" ); - (void)dbi_conn_connect( dbi_conn->conn ); + do { + gnc_dbi_init_error( dbi_conn ); result = dbi_conn_queryf( dbi_conn->conn, "BEGIN" ); - } + } while( dbi_conn->retry ); status = dbi_result_free( result ); if ( status < 0 ) @@ -2017,6 +2075,8 @@ create_dbi_connection( /*@ observer @*/ provider_functions_t* provider, dbi_conn->conn = conn; dbi_conn->provider = provider; + gnc_dbi_init_error(dbi_conn); + return (GncSqlConnection*)dbi_conn; }