commit 0e86dfd5135d9ee706224be894915ab873a57a77
parent 74add07b81510d98e76ac33353304ddf05500781
Author: Christian Grothoff <grothoff@gnunet.org>
Date: Tue, 14 Jul 2026 23:36:29 +0200
retry outer transaction instead of aborting on inner statement on serialization failures
Diffstat:
1 file changed, 39 insertions(+), 11 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-abort.c b/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-abort.c
@@ -198,6 +198,16 @@ struct AbortContext
const char *current_exchange;
/**
+ * Status of the database operations performed by the
+ * #refund_coins() row callback. Set to
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT before starting the
+ * iteration over the deposits, and to the failed query status if
+ * one of the refund_coin() operations failed. Inspected by
+ * #begin_transaction() once the iteration has finished.
+ */
+ enum GNUNET_DB_QueryStatus row_qs;
+
+ /**
* Number of coins this abort is for. Length of the @e rd array.
*/
size_t coins_cnt;
@@ -652,6 +662,10 @@ find_next_exchange (struct AbortContext *ac)
/**
* Function called with information about a coin that was deposited.
+ * Note that we must not roll back or restart the transaction here, as
+ * we are called from within the iteration over the deposits. Failures
+ * are merely recorded in @a ac->row_qs and then handled by
+ * #begin_transaction() once the iteration has completed.
*
* @param cls closure
* @param exchange_url exchange where @a coin_pub was deposited
@@ -676,6 +690,8 @@ refund_coins (void *cls,
(void) deposit_fee;
(void) refund_fee;
(void) wire_fee;
+ if (0 > ac->row_qs)
+ return; /* already failed, do not touch the DB any further */
now = GNUNET_TIME_timestamp_get ();
for (size_t i = 0; i<ac->coins_cnt; i++)
{
@@ -701,18 +717,9 @@ refund_coins (void *cls,
"incomplete abortment aborted");
if (0 > qs)
{
- TALER_MERCHANTDB_rollback (TMH_db);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- begin_transaction (ac);
- return;
- }
- /* Always report on hard error as well to enable diagnostics */
+ /* Always report on hard error to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- resume_abort_with_error (ac,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "refund_coin");
+ ac->row_qs = qs;
return;
}
} /* for all coins */
@@ -832,6 +839,7 @@ begin_transaction (struct AbortContext *ac)
}
/* Mark all deposits we have in our database for the order as refunded. */
+ ac->row_qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
qs = TALER_MERCHANTDB_lookup_deposits (TMH_db,
ac->hc->instance->settings.id,
&ac->h_contract_terms,
@@ -853,6 +861,26 @@ begin_transaction (struct AbortContext *ac)
"deposits");
goto cleanup;
}
+ /* Check if one of the refund_coin() operations done by the
+ #refund_coins() row callback failed; we must handle this here (and
+ not in the callback) as we may only roll back and restart the
+ transaction from outside of the row iteration. */
+ if (0 > ac->row_qs)
+ {
+ TALER_MERCHANTDB_rollback (TMH_db);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == ac->row_qs)
+ {
+ begin_transaction (ac);
+ goto cleanup;
+ }
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == ac->row_qs);
+ resume_abort_with_error (ac,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "refund_coin");
+ goto cleanup;
+ }
qs = TALER_MERCHANTDB_commit (TMH_db);
if (0 > qs)