From 75b3065db390c1c631accd03b68f162953597733 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 17 Sep 2021 00:34:52 +0200 Subject: expose DB garbage collection, ROLLBACK instead of committing if preflight check fails --- src/include/anastasis_database_plugin.h | 7 ++- src/stasis/anastasis-dbinit.c | 46 ++++++++++++++++--- src/stasis/plugin_anastasis_postgres.c | 78 ++++++++++++++++++++++++++++++--- 3 files changed, 119 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/include/anastasis_database_plugin.h b/src/include/anastasis_database_plugin.h index bc4b0e6..7bf91a2 100644 --- a/src/include/anastasis_database_plugin.h +++ b/src/include/anastasis_database_plugin.h @@ -232,9 +232,12 @@ struct ANASTASIS_DatabasePlugin * Does not return anything, as we will continue regardless of the outcome. * * @param cls the `struct PostgresClosure` with the plugin-specific state + * @return #GNUNET_OK if everything is fine + * #GNUNET_NO if a transaction was rolled back + * #GNUNET_SYSERR on hard errors */ - void - (*preflight) (void *cls); + enum GNUNET_GenericReturnValue + (*preflight)(void *cls); /** * Check that the database connection is still up. diff --git a/src/stasis/anastasis-dbinit.c b/src/stasis/anastasis-dbinit.c index 17b3c56..a0e17db 100644 --- a/src/stasis/anastasis-dbinit.c +++ b/src/stasis/anastasis-dbinit.c @@ -33,6 +33,11 @@ static int global_ret; */ static int reset_db; +/** + * -g option: do garbate collection + */ +static int gc_db; + /** * Main function that will be run. * @@ -54,21 +59,49 @@ run (void *cls, { fprintf (stderr, "Failed to initialize database plugin.\n"); - global_ret = EXIT_FAILURE; + global_ret = EXIT_NOTINSTALLED; return; } if (reset_db) { - (void) plugin->drop_tables (plugin->cls); - ANASTASIS_DB_plugin_unload (plugin); - plugin = ANASTASIS_DB_plugin_load (cfg); + if (GNUNET_OK != plugin->drop_tables (plugin->cls)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not drop tables as requested. Either database was not yet initialized, or permission denied. Consult the logs. Will still try to create new tables.\n"); + } } if (GNUNET_OK != plugin->create_tables (plugin->cls)) { global_ret = EXIT_FAILURE; + ANASTASIS_DB_plugin_unload (plugin); return; } + if (gc_db) + { + struct GNUNET_TIME_Absolute expire_backups; + struct GNUNET_TIME_Absolute expire_payments; + struct GNUNET_TIME_Absolute now; + + now = GNUNET_TIME_absolute_get (); + expire_backups = GNUNET_TIME_absolute_subtract ( + now, + GNUNET_TIME_relative_multiply ( + GNUNET_TIME_UNIT_MONTHS, + 6)); + expire_payments = GNUNET_TIME_absolute_subtract ( + now, + GNUNET_TIME_relative_multiply ( + GNUNET_TIME_UNIT_YEARS, + 10)); + if (0 > plugin->gc (plugin->cls, + expire_backups, + expire_payments)) + { + fprintf (stderr, + "Garbage collection failed!\n"); + } + } ANASTASIS_DB_plugin_unload (plugin); } @@ -86,7 +119,10 @@ main (int argc, char *const *argv) { struct GNUNET_GETOPT_CommandLineOption options[] = { - + GNUNET_GETOPT_option_flag ('g', + "garbagecollect", + "remove state data from database", + &gc_db), GNUNET_GETOPT_option_flag ('r', "reset", "reset database (DANGEROUS: all existing data is lost!)", diff --git a/src/stasis/plugin_anastasis_postgres.c b/src/stasis/plugin_anastasis_postgres.c index b78dbdb..b1be081 100644 --- a/src/stasis/plugin_anastasis_postgres.c +++ b/src/stasis/plugin_anastasis_postgres.c @@ -72,6 +72,10 @@ struct PostgresClosure */ char *currency; + /** + * Prepared statements have been initialized. + */ + bool init; }; @@ -523,39 +527,103 @@ check_connection (void *cls) } +/** + * Connect to the database if the connection does not exist yet. + * + * @param pg the plugin-specific state + * @param skip_prepare true if we should skip prepared statement setup + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +internal_setup (struct PostgresClosure *pg, + bool skip_prepare) +{ + if (NULL == pg->conn) + { +#if AUTO_EXPLAIN + /* Enable verbose logging to see where queries do not + properly use indices */ + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_try_execute ("LOAD 'auto_explain';"), + GNUNET_PQ_make_try_execute ("SET auto_explain.log_min_duration=50;"), + GNUNET_PQ_make_try_execute ("SET auto_explain.log_timing=TRUE;"), + GNUNET_PQ_make_try_execute ("SET auto_explain.log_analyze=TRUE;"), + /* https://wiki.postgresql.org/wiki/Serializable suggests to really + force the default to 'serializable' if SSI is to be used. */ + GNUNET_PQ_make_try_execute ( + "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"), + GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"), + GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; +#else + struct GNUNET_PQ_ExecuteStatement *es = NULL; +#endif + struct GNUNET_PQ_Context *db_conn; + + db_conn = GNUNET_PQ_connect_with_cfg (pg->cfg, + "exchangedb-postgres", + NULL, + es, + NULL); + if (NULL == db_conn) + return GNUNET_SYSERR; + pg->conn = db_conn; + } + if (NULL == pg->transaction_name) + GNUNET_PQ_reconnect_if_down (pg->conn); + if (pg->init) + return GNUNET_OK; + if (skip_prepare) + return GNUNET_OK; + return postgres_connect (pg); +} + + /** * Do a pre-flight check that we are not in an uncommitted transaction. * If we are, try to commit the previous transaction and output a warning. * Does not return anything, as we will continue regardless of the outcome. * * @param cls the `struct PostgresClosure` with the plugin-specific state + * @return #GNUNET_OK if everything is fine + * #GNUNET_NO if a transaction was rolled back + * #GNUNET_SYSERR on hard errors */ -static void +static enum GNUNET_GenericReturnValue postgres_preflight (void *cls) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_ExecuteStatement es[] = { - GNUNET_PQ_make_execute ("COMMIT"), + GNUNET_PQ_make_execute ("ROLLBACK"), GNUNET_PQ_EXECUTE_STATEMENT_END }; + if (! pg->init) + { + if (GNUNET_OK != + internal_setup (pg, + false)) + return GNUNET_SYSERR; + } if (NULL == pg->transaction_name) - return; /* all good */ + return GNUNET_OK; /* all good */ if (GNUNET_OK == GNUNET_PQ_exec_statements (pg->conn, es)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "BUG: Preflight check committed transaction `%s'!\n", + "BUG: Preflight check rolled back transaction `%s'!\n", pg->transaction_name); } else { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "BUG: Preflight check failed to commit transaction `%s'!\n", + "BUG: Preflight check failed to rollback transaction `%s'!\n", pg->transaction_name); } pg->transaction_name = NULL; + return GNUNET_NO; } -- cgit v1.2.3