From e140ca9dcef9bd86f9c9214872107693ef8c441c Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 17 Nov 2016 15:53:16 +0100 Subject: [PATCH] handle more nicely the case that concurrent withdraws have changed history, causing us to see a different balance just before the commit; in this case, just retry the transaction; this should fix #4794 --- src/exchange/taler-exchange-httpd_db.c | 32 ++++++++++++++++++--- src/exchangedb/plugin_exchangedb_postgres.c | 11 +++++-- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c index 848d28822..2c6e90656 100644 --- a/src/exchange/taler-exchange-httpd_db.c +++ b/src/exchange/taler-exchange-httpd_db.c @@ -91,6 +91,27 @@ transaction_start_label: /* we will use goto for retries */ \ } /* end of scope opened by BEGIN_TRANSACTION */ +/** + * Code to include to retry a transaction, must only be used in between + * #START_TRANSACTION and #COMMIT_TRANSACTION. + * + * @param session session handle + * @param connection connection handle + */ +#define RETRY_TRANSACTION(session,connection) \ + do { \ + TEH_plugin->rollback (TEH_plugin->cls, \ + session); \ + if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \ + goto transaction_start_label; \ + TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \ + transaction_retries, \ + __FUNCTION__); \ + return TEH_RESPONSE_reply_commit_error (connection, \ + TALER_EC_DB_COMMIT_FAILED_ON_RETRY); \ + } while (0) + + /** * Calculate the total value of all transactions performed. * Stores @a off plus the cost of all transactions in @a tl @@ -647,6 +668,7 @@ execute_reserve_withdraw_transaction (struct MHD_Connection *connection, struct TALER_Amount value; struct TALER_Amount fee_withdraw; int res; + int ret; /* Check if balance is sufficient */ START_TRANSACTION (session, connection); @@ -794,10 +816,10 @@ execute_reserve_withdraw_transaction (struct MHD_Connection *connection, collectable.reserve_pub = *reserve; collectable.h_coin_envelope = *h_blind; collectable.reserve_sig = *signature; - if (GNUNET_OK != - TEH_plugin->insert_withdraw_info (TEH_plugin->cls, - session, - &collectable)) + ret = TEH_plugin->insert_withdraw_info (TEH_plugin->cls, + session, + &collectable); + if (GNUNET_SYSERR == ret) { GNUNET_break (0); TEH_plugin->rollback (TEH_plugin->cls, @@ -805,6 +827,8 @@ execute_reserve_withdraw_transaction (struct MHD_Connection *connection, return TEH_RESPONSE_reply_internal_db_error (connection, TALER_EC_WITHDRAW_DB_STORE_ERROR); } + if (GNUNET_NO == ret) + RETRY_TRANSACTION(session, connection); COMMIT_TRANSACTION (session, connection); return TEH_RESPONSE_reply_reserve_withdraw_success (connection, diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 0a7f93f77..7ae8b5753 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -1971,7 +1971,7 @@ postgres_get_withdraw_info (void *cls, * @param collectable corresponding collectable coin (blind signature) * if a coin is found * @return #GNUNET_SYSERR on internal error - * #GNUNET_NO if the collectable was not found + * #GNUNET_NO if we failed but should retry the transaction * #GNUNET_YES on success */ static int @@ -2018,8 +2018,13 @@ postgres_insert_withdraw_info (void *cls, &reserve.balance, &collectable->amount_with_fee)) { - /* Should have been checked before we got here... */ - GNUNET_break (0); /* FIXME: this actually happens: #4794 */ + /* The reserve history was checked to make sure there is enough of a balance + left before we tried this; however, concurrent operations may have changed + the situation by now. We should re-try the transaction. */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Withdrawal from reserve `%s' refused due to balance missmatch. Retrying.\n", + TALER_B2S (&collectable->reserve_pub)); + ret = GNUNET_NO; goto cleanup; } expiry = GNUNET_TIME_absolute_add (now,