exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit 611c9160b9b35a28c43972b561bc08a62c3da1fa
parent fd8b151a7e6689fa1105b034aa9527e1cf1c3e56
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu,  6 Aug 2026 22:51:48 +0200

handle DB errors better

Diffstat:
Msrc/exchange/taler-exchange-aggregator.c | 7++++---
Msrc/exchange/taler-exchange-expire.c | 24+++++++++++++++++++++++-
Msrc/exchange/taler-exchange-httpd_delete-purses-PURSE_PUB.c | 35+++++++++++++++++++++++++++++------
Msrc/exchange/taler-exchange-router.c | 1+
4 files changed, 57 insertions(+), 10 deletions(-)

diff --git a/src/exchange/taler-exchange-aggregator.c b/src/exchange/taler-exchange-aggregator.c @@ -1517,12 +1517,13 @@ drain_kyc_alerts (void *cls) switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to lookup transient aggregates!\n"); TALER_EXCHANGEDB_rollback (pg); - GNUNET_assert (NULL == task); - task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts, - NULL); + GNUNET_free (au); + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); return; case GNUNET_DB_STATUS_SOFT_ERROR: /* serializiability issue, try again */ diff --git a/src/exchange/taler-exchange-expire.c b/src/exchange/taler-exchange-expire.c @@ -254,8 +254,30 @@ abort_shard (struct Shard *s) "expire", s->shard_start.abs_value_us, s->shard_end.abs_value_us); - if (0 >= qs) + switch (qs) { + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + /* good case, handled below */ + break; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* Somebody else already reclaimed the shard; not our problem, and + certainly not a reason to terminate. */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Shard to abort was already released\n"); + GNUNET_free (s); + return; + case GNUNET_DB_STATUS_SOFT_ERROR: + /* This is reached from the soft-error path of run_shard(), i.e. exactly + when contention is highest. The shard lock times out on its own, so + shutting the daemon down here does more harm than good, + let's just continue. */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Failed to abort shard due to serialization failure; the " + "lock will time out on its own\n"); + GNUNET_free (s); + return; + case GNUNET_DB_STATUS_HARD_ERROR: + /* Serious database issue, probably best to exit */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to abort shard (%d)!\n", qs); diff --git a/src/exchange/taler-exchange-httpd_delete-purses-PURSE_PUB.c b/src/exchange/taler-exchange-httpd_delete-purses-PURSE_PUB.c @@ -35,6 +35,11 @@ #include "exchange-database/do_purse_delete.h" #include "exchange-database/event_notify.h" +/** + * How often do we retry a transaction that failed to serialize? + */ +#define MAX_RETRIES 3 + enum MHD_Result TEH_handler_purses_delete ( @@ -83,13 +88,31 @@ TEH_handler_purses_delete ( } { - enum GNUNET_DB_QueryStatus qs; + enum GNUNET_DB_QueryStatus qs = GNUNET_DB_STATUS_SOFT_ERROR; - qs = TALER_EXCHANGEDB_do_purse_delete (TEH_pg, - &purse_pub, - &purse_sig, - &decided, - &found); + /* Under SERIALIZABLE a 40001 is routine, so retry rather than answering + the client with an internal error; this file has no retry loop of its + own and used to lump SOFT_ERROR in with HARD_ERROR (and with + SUCCESS_NO_RESULTS) via "qs <= 0". */ + for (unsigned int i = 0; i<MAX_RETRIES; i++) + { + qs = TALER_EXCHANGEDB_do_purse_delete (TEH_pg, + &purse_pub, + &purse_sig, + &decided, + &found); + if (GNUNET_DB_STATUS_SOFT_ERROR != qs) + break; + } + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + TALER_LOG_WARNING ( + "Repeated serialization failure deleting purse\n"); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_SOFT_FAILURE, + "purse delete"); + } if (qs <= 0) { TALER_LOG_WARNING ( diff --git a/src/exchange/taler-exchange-router.c b/src/exchange/taler-exchange-router.c @@ -267,6 +267,7 @@ release_shard (struct Shard *s) case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); GNUNET_break (0); + global_ret = EXIT_FAILURE; GNUNET_SCHEDULER_shutdown (); return; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: