merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

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:
Msrc/backend/taler-merchant-httpd_post-orders-ORDER_ID-abort.c | 50+++++++++++++++++++++++++++++++++++++++-----------
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)