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:
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;
}