r19738 - gnucash/trunk/src/backend/dbi - Bug 629238: Part 1: Create a lock table for postgresql and mysql backends.
John Ralls
jralls at code.gnucash.org
Sun Oct 31 16:31:28 EDT 2010
Author: jralls
Date: 2010-10-31 16:31:27 -0400 (Sun, 31 Oct 2010)
New Revision: 19738
Trac: http://svn.gnucash.org/trac/changeset/19738
Modified:
gnucash/trunk/src/backend/dbi/gnc-backend-dbi.c
Log:
Bug 629238: Part 1: Create a lock table for postgresql and mysql backends.
Modified: gnucash/trunk/src/backend/dbi/gnc-backend-dbi.c
===================================================================
--- gnucash/trunk/src/backend/dbi/gnc-backend-dbi.c 2010-10-31 17:15:51 UTC (rev 19737)
+++ gnucash/trunk/src/backend/dbi/gnc-backend-dbi.c 2010-10-31 20:31:27 UTC (rev 19738)
@@ -58,6 +58,19 @@
#include "splint-defs.h"
#endif
+#ifdef WIN32
+#include <Winsock2.h>
+#define HOST_NAME_MAX 255
+#define GETPID() GetCurrentProcessId()
+#else
+#include <limits.h>
+#include <unistd.h>
+#ifdef DARWIN
+#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
+#endif
+#define GETPID() getpid()
+#endif
+
#define TRANSACTION_NAME "trans"
static QofLogModule log_module = G_LOG_DOMAIN;
@@ -290,6 +303,7 @@
basename = g_path_get_basename( filepath );
g_free ( filepath );
dbi_conn_error_handler( be->conn, sqlite3_error_fn, be );
+/* dbi-sqlite3 documentation says that sqlite3 doesn't take a "host" option */
result = dbi_conn_set_option( be->conn, "host", "localhost" );
if ( result < 0 )
{
@@ -317,6 +331,7 @@
result = dbi_conn_connect( be->conn );
g_free( basename );
g_free( dirname );
+/* Need some better error handling here. In particular, need to emit a QOF_ERROR_LOCKED if the database is in use by another process. */
if ( result < 0 )
{
PERR( "Unable to connect to %s: %d\n", book_id, result );
@@ -437,10 +452,216 @@
return TRUE;
}
+
+static gboolean
+gnc_dbi_lock_database ( QofBackend* qbe, gboolean ignore_lock )
+{
+
+ GncDbiBackend *qe = (GncDbiBackend*)qbe;
+ dbi_conn dcon = qe->conn;
+ dbi_result result;
+ const gchar *dbname = dbi_conn_get_option( dcon, "dbname" );
+/* Create the table if it doesn't exist */
+ result = dbi_conn_get_table_list( dcon, dbname, "GNCLOCK");
+ if (!( result && dbi_result_get_numrows( result ) ))
+ {
+ if ( result )
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ result = dbi_conn_queryf( dcon, "CREATE TABLE GNCLOCK ( Hostname varchar(%d), PID int )", HOST_NAME_MAX );
+ if ( dbi_conn_error_flag( dcon ) )
+ {
+ const gchar *errstr;
+ dbi_conn_error( dcon, &errstr );
+ PERR( "Error %s creating lock table", errstr );
+ qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
+ return FALSE;
+ }
+ if ( result )
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ }
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+
+/* Protect everything with a single transaction to prevent races */
+ if ( (result = dbi_conn_query( dcon, "BEGIN" )) )
+ {
+/* Check for an existing entry; delete it if ignore_lock is true, otherwise fail */
+ gchar hostname[ HOST_NAME_MAX + 1 ];
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ result = dbi_conn_query( dcon, "SELECT * FROM GNCLOCK" );
+ if ( result && dbi_result_get_numrows( result ) )
+ {
+ dbi_result_free( result );
+ result = NULL;
+ if ( !ignore_lock )
+ {
+ qof_backend_set_error( qbe, ERR_BACKEND_LOCKED );
+/* FIXME: After enhancing the qof_backend_error mechanism, report in the dialog what is the hostname of the machine holding the lock. */
+ dbi_conn_query( dcon, "ROLLBACK" );
+ return FALSE;
+ }
+ result = dbi_conn_query( dcon, "DELETE FROM GNCLOCK" );
+ if ( !result)
+ {
+ qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
+ result = dbi_conn_query( dcon, "ROLLBACK" );
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ return FALSE;
+ }
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ }
+/* Add an entry and commit the transaction */
+ memset( hostname, 0, sizeof(hostname) );
+ gethostname( hostname, HOST_NAME_MAX );
+ result = dbi_conn_queryf( dcon,
+ "INSERT INTO GNCLOCK VALUES ('%s', '%d')",
+ hostname, (int)GETPID() );
+ if ( !result)
+ {
+ qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
+ result = dbi_conn_query( dcon, "ROLLBACK" );
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ return FALSE;
+ }
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ result = dbi_conn_query( dcon, "COMMIT" );
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ return TRUE;
+ }
+/* Couldn't get a transaction (probably couldn't get a lock), so fail */
+ qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ return FALSE;
+}
+
static void
+gnc_dbi_unlock( QofBackend *qbe )
+{
+ GncDbiBackend *qe = (GncDbiBackend*)qbe;
+ dbi_conn dcon = qe->conn;
+ dbi_result result;
+ const gchar *dbname = NULL;
+
+ g_return_if_fail( dcon != NULL );
+ g_return_if_fail( dbi_conn_error_flag( dcon ) == 0 );
+
+ dbname = dbi_conn_get_option( dcon, "dbname" );
+/* Check if the lock table exists */
+ result = dbi_conn_get_table_list( dcon, dbname, "GNCLOCK");
+ if (!( result && dbi_result_get_numrows( result ) ))
+ {
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ PWARN("No lock table in database, so not unlocking it.");
+ return;
+ }
+
+ if ( ( result = dbi_conn_query( dcon, "BEGIN" )) )
+ {
+/* Delete the entry if it's our hostname and PID */
+ gchar hostname[ HOST_NAME_MAX + 1 ];
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ memset( hostname, 0, sizeof(hostname) );
+ gethostname( hostname, HOST_NAME_MAX );
+ result = dbi_conn_queryf( dcon, "SELECT * FROM GNCLOCK WHERE Hostname = '%s' AND PID = '%d'", hostname, (int)GETPID() );
+ if ( result && dbi_result_get_numrows( result ) )
+ {
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ result = dbi_conn_query( dcon, "DELETE FROM GNCLOCK" );
+ if ( !result)
+ {
+ PERR("Failed to delete the lock entry");
+ qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
+ result = dbi_conn_query( dcon, "ROLLBACK" );
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ return;
+ }
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ result = dbi_conn_query( dcon, "COMMIT" );
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ return;
+ }
+ result = dbi_conn_query( dcon, "ROLLBACK" );
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ PWARN("There was no lock entry in the Lock table");
+ return;
+ }
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ PWARN("Unable to get a lock on LOCK, so failed to clear the lock entry.");
+ qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
+}
+
+static void
gnc_dbi_mysql_session_begin( QofBackend* qbe, QofSession *session,
- const gchar *book_id,
- /*@ unused @*/gboolean ignore_lock,
+ const gchar *book_id, gboolean ignore_lock,
gboolean create_if_nonexistent )
{
GncDbiBackend *be = (GncDbiBackend*)qbe;
@@ -488,7 +709,7 @@
result = dbi_conn_connect( be->conn );
if ( result == 0 )
{
- success = TRUE;
+ success = gnc_dbi_lock_database ( qbe, ignore_lock );
}
else
{
@@ -547,7 +768,7 @@
qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
goto exit;
}
- success = TRUE;
+ success = gnc_dbi_lock_database ( qbe, ignore_lock );
}
else
{
@@ -634,8 +855,7 @@
static void
gnc_dbi_postgres_session_begin( QofBackend *qbe, QofSession *session,
- const gchar *book_id,
- /*@ unused @*/ gboolean ignore_lock,
+ const gchar *book_id, gboolean ignore_lock,
gboolean create_if_nonexistent )
{
GncDbiBackend *be = (GncDbiBackend*)qbe;
@@ -685,7 +905,7 @@
result = dbi_conn_connect( be->conn );
if ( result == 0 )
{
- success = TRUE;
+ success = gnc_dbi_lock_database ( qbe, ignore_lock );
}
else
{
@@ -744,7 +964,7 @@
qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
goto exit;
}
- success = TRUE;
+ success = gnc_dbi_lock_database ( qbe, ignore_lock );
}
else
{
@@ -784,6 +1004,7 @@
if ( be->conn != NULL )
{
+ gnc_dbi_unlock( be_start );
dbi_conn_close( be->conn );
be->conn = NULL;
}
@@ -888,7 +1109,11 @@
{
const gchar* table_name = (const gchar*)node->data;
dbi_result result;
-
+ /* Don't delete the lock table */
+ if ( g_strcmp0(table_name, "GNCLOCK") == 0)
+ {
+ continue;
+ }
do
{
gnc_dbi_init_error( ((GncDbiSqlConnection*)(be->sql_be.conn)) );
More information about the gnucash-changes
mailing list