start.c (5417B)
1 /* 2 This file is part of TALER 3 (C) 2014--2025 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Lesser General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file src/backenddb/start.c 18 * @brief implementation of the start, rollback and commit functions 19 * @author Christian Grothoff 20 */ 21 #include <gnunet/gnunet_util_lib.h> 22 #include "merchantdb_lib.h" 23 #include "merchant-database/start.h" 24 #include "merchant-database/preflight.h" 25 #include "helper.h" 26 27 28 enum GNUNET_GenericReturnValue 29 TALER_MERCHANTDB_start ( 30 struct TALER_MERCHANTDB_PostgresContext *pg, 31 const char *name) 32 { 33 struct GNUNET_PQ_ExecuteStatement es[] = { 34 GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"), 35 GNUNET_PQ_EXECUTE_STATEMENT_END 36 }; 37 38 GNUNET_assert (NULL != name); 39 TALER_MERCHANTDB_preflight (pg); 40 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 41 "Starting merchant DB transaction `%s'\n", 42 name); 43 if (GNUNET_OK != 44 GNUNET_PQ_exec_statements (pg->conn, 45 es)) 46 { 47 TALER_LOG_ERROR ("Failed to start transaction\n"); 48 GNUNET_break (0); 49 return GNUNET_SYSERR; 50 } 51 pg->transaction_name = name; 52 return GNUNET_OK; 53 } 54 55 56 enum GNUNET_GenericReturnValue 57 TALER_MERCHANTDB_start_read_committed ( 58 struct TALER_MERCHANTDB_PostgresContext *pg, 59 const char *name) 60 { 61 struct GNUNET_PQ_ExecuteStatement es[] = { 62 GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL READ COMMITTED"), 63 GNUNET_PQ_EXECUTE_STATEMENT_END 64 }; 65 66 GNUNET_assert (NULL != name); 67 TALER_MERCHANTDB_preflight (pg); 68 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 69 "Starting merchant DB transaction %s (READ COMMITTED)\n", 70 name); 71 if (GNUNET_OK != 72 GNUNET_PQ_exec_statements (pg->conn, 73 es)) 74 { 75 TALER_LOG_ERROR ("Failed to start transaction\n"); 76 GNUNET_break (0); 77 return GNUNET_SYSERR; 78 } 79 pg->transaction_name = name; 80 return GNUNET_OK; 81 } 82 83 84 void 85 TALER_MERCHANTDB_rollback ( 86 struct TALER_MERCHANTDB_PostgresContext *pg) 87 { 88 struct GNUNET_PQ_ExecuteStatement es[] = { 89 GNUNET_PQ_make_execute ("ROLLBACK"), 90 GNUNET_PQ_EXECUTE_STATEMENT_END 91 }; 92 93 if (NULL == pg->transaction_name) 94 return; 95 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 96 "Rolling back merchant DB transaction `%s'\n", 97 pg->transaction_name); 98 GNUNET_break (GNUNET_OK == 99 GNUNET_PQ_exec_statements (pg->conn, 100 es)); 101 pg->transaction_name = NULL; 102 } 103 104 105 enum GNUNET_DB_QueryStatus 106 TALER_MERCHANTDB_commit ( 107 struct TALER_MERCHANTDB_PostgresContext *pg) 108 { 109 struct GNUNET_PQ_QueryParam params[] = { 110 GNUNET_PQ_query_param_end 111 }; 112 enum GNUNET_DB_QueryStatus qs; 113 114 GNUNET_break (NULL != pg->transaction_name); 115 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 116 "Committing transaction `%s'\n", 117 pg->transaction_name); 118 PREPARE (pg, 119 "commit", 120 "COMMIT"); 121 { 122 PGresult *result; 123 124 result = GNUNET_PQ_exec_prepared (pg->conn, 125 "commit", 126 params); 127 qs = GNUNET_PQ_eval_result (pg->conn, 128 "commit", 129 result); 130 /* PostgreSQL accepts COMMIT on a transaction it has already aborted: it 131 silently rolls the transaction back and answers PGRES_COMMAND_OK with the 132 command tag ROLLBACK. GNUNET_PQ_eval_result() cannot tell that apart from 133 a real commit, so every caller took the success path for a transaction 134 whose writes are gone. Report it as a soft error instead: sessions run 135 SERIALIZABLE, so the overwhelmingly likely cause is an unhandled 136 40001/40P01 for which retrying is right; a caller with a retry loop gives 137 up after MAX_RETRIES and fails visibly, one without fails immediately. 138 139 The *success* path deliberately keeps returning exactly what 140 GNUNET_PQ_eval_prepared_non_select() returned before. That function is 141 GNUNET_PQ_exec_prepared() + GNUNET_PQ_eval_result() plus a 142 strtol(PQcmdTuples()) step that only runs when the status is already 143 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS, and PQcmdTuples() on a COMMIT command 144 tag is the empty string -- so that step yields 0, which is 145 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS again. */ 146 if ( (0 <= qs) && 147 (NULL != result) && 148 (0 == strcmp ("ROLLBACK", 149 PQcmdStatus (result))) ) 150 { 151 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 152 "Transaction `%s' was rolled back by the database instead of committed; a previous statement must have failed without being handled\n", 153 pg->transaction_name); 154 qs = GNUNET_DB_STATUS_SOFT_ERROR; 155 } 156 PQclear (result); 157 } 158 pg->transaction_name = NULL; 159 return qs; 160 }