anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

commit 5f65dc323c823e41412d1e802224adf7d18b2441
parent 1f8c8ce04a552a6352cb8074d769a9a70e8fadb2
Author: Christian Grothoff <christian@grothoff.org>
Date:   Fri,  7 Aug 2026 21:06:55 +0200

common commit-after-rollback failure handling fix

Diffstat:
Msrc/stasis/anastasis-db_pg.c | 40+++++++++++++++++++++++++++++++++++++---
1 file changed, 37 insertions(+), 3 deletions(-)

diff --git a/src/stasis/anastasis-db_pg.c b/src/stasis/anastasis-db_pg.c @@ -90,9 +90,43 @@ ANASTASIS_DB_commit (void) PREPARE ("do_commit", "COMMIT"); - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "do_commit", - no_params); + { + PGresult *result; + + result = GNUNET_PQ_exec_prepared (pg->conn, + "do_commit", + no_params); + qs = GNUNET_PQ_eval_result (pg->conn, + "do_commit", + result); + /* PostgreSQL accepts COMMIT on a transaction it has already aborted: it + silently rolls the transaction back and answers PGRES_COMMAND_OK with the + command tag ROLLBACK. GNUNET_PQ_eval_result() cannot tell that apart from + a real commit, so every caller took the success path for a transaction + whose writes are gone. Report it as a soft error instead: sessions run + SERIALIZABLE, so the overwhelmingly likely cause is an unhandled + 40001/40P01 for which retrying is right; a caller with a retry loop gives + up after MAX_RETRIES and fails visibly, one without fails immediately. + + The *success* path deliberately keeps returning exactly what + GNUNET_PQ_eval_prepared_non_select() returned before. That function is + GNUNET_PQ_exec_prepared() + GNUNET_PQ_eval_result() plus a + strtol(PQcmdTuples()) step that only runs when the status is already + GNUNET_DB_STATUS_SUCCESS_NO_RESULTS, and PQcmdTuples() on a COMMIT command + tag is the empty string -- so that step yields 0, which is + GNUNET_DB_STATUS_SUCCESS_NO_RESULTS again. */ + if ( (0 <= qs) && + (NULL != result) && + (0 == strcmp ("ROLLBACK", + PQcmdStatus (result))) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Transaction `%s' was rolled back by the database instead of committed; a previous statement must have failed without being handled\n", + pg->transaction_name); + qs = GNUNET_DB_STATUS_SOFT_ERROR; + } + PQclear (result); + } pg->transaction_name = NULL; return qs; }