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

This commit is contained in:
Christian Grothoff 2016-11-17 15:53:16 +01:00
parent bb7c58921e
commit e140ca9dce
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
2 changed files with 36 additions and 7 deletions

View File

@ -91,6 +91,27 @@ transaction_start_label: /* we will use goto for retries */ \
} /* end of scope opened by BEGIN_TRANSACTION */ } /* 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. * Calculate the total value of all transactions performed.
* Stores @a off plus the cost of all transactions in @a tl * 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 value;
struct TALER_Amount fee_withdraw; struct TALER_Amount fee_withdraw;
int res; int res;
int ret;
/* Check if balance is sufficient */ /* Check if balance is sufficient */
START_TRANSACTION (session, connection); START_TRANSACTION (session, connection);
@ -794,10 +816,10 @@ execute_reserve_withdraw_transaction (struct MHD_Connection *connection,
collectable.reserve_pub = *reserve; collectable.reserve_pub = *reserve;
collectable.h_coin_envelope = *h_blind; collectable.h_coin_envelope = *h_blind;
collectable.reserve_sig = *signature; collectable.reserve_sig = *signature;
if (GNUNET_OK != ret = TEH_plugin->insert_withdraw_info (TEH_plugin->cls,
TEH_plugin->insert_withdraw_info (TEH_plugin->cls, session,
session, &collectable);
&collectable)) if (GNUNET_SYSERR == ret)
{ {
GNUNET_break (0); GNUNET_break (0);
TEH_plugin->rollback (TEH_plugin->cls, 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, return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_WITHDRAW_DB_STORE_ERROR); TALER_EC_WITHDRAW_DB_STORE_ERROR);
} }
if (GNUNET_NO == ret)
RETRY_TRANSACTION(session, connection);
COMMIT_TRANSACTION (session, connection); COMMIT_TRANSACTION (session, connection);
return TEH_RESPONSE_reply_reserve_withdraw_success (connection, return TEH_RESPONSE_reply_reserve_withdraw_success (connection,

View File

@ -1971,7 +1971,7 @@ postgres_get_withdraw_info (void *cls,
* @param collectable corresponding collectable coin (blind signature) * @param collectable corresponding collectable coin (blind signature)
* if a coin is found * if a coin is found
* @return #GNUNET_SYSERR on internal error * @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 * #GNUNET_YES on success
*/ */
static int static int
@ -2018,8 +2018,13 @@ postgres_insert_withdraw_info (void *cls,
&reserve.balance, &reserve.balance,
&collectable->amount_with_fee)) &collectable->amount_with_fee))
{ {
/* Should have been checked before we got here... */ /* The reserve history was checked to make sure there is enough of a balance
GNUNET_break (0); /* FIXME: this actually happens: #4794 */ 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; goto cleanup;
} }
expiry = GNUNET_TIME_absolute_add (now, expiry = GNUNET_TIME_absolute_add (now,