anastasis-db_pg.c (8841B)
1 /* 2 This file is part of Anastasis 3 Copyright (C) 2020, 2021, 2022 Anastasis SARL 4 5 Anastasis is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 Anastasis 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 Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file stasis/anastasis-db_pg.c 18 * @brief shared database state and helpers for the Anastasis postgres backend 19 * @author Christian Grothoff 20 * @author Marcello Stanisci 21 */ 22 #include "platform.h" 23 #include "anastasis-db_pg.h" 24 #include "anastasis/anastasis-database/preflight.h" 25 #include "anastasis/anastasis-database/transaction.h" 26 #include "anastasis/anastasis-database/event.h" 27 #include <taler/taler_pq_lib.h> 28 29 30 /** 31 * Prepared statements have been initialized in this edition. 32 */ 33 uint64_t ANASTASIS_DB_prep_gen_; 34 35 /** 36 * Global database state. 37 */ 38 struct PostgresClosure *pg; 39 40 41 enum GNUNET_GenericReturnValue 42 ANASTASIS_DB_start (const char *name) 43 { 44 struct GNUNET_PQ_ExecuteStatement es[] = { 45 GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"), 46 GNUNET_PQ_EXECUTE_STATEMENT_END 47 }; 48 49 GNUNET_break (GNUNET_OK == 50 ANASTASIS_DB_preflight ()); 51 pg->transaction_name = name; 52 if (GNUNET_OK != 53 GNUNET_PQ_exec_statements (pg->conn, 54 es)) 55 { 56 TALER_LOG_ERROR ("Failed to start transaction\n"); 57 GNUNET_break (0); 58 return GNUNET_SYSERR; 59 } 60 return GNUNET_OK; 61 } 62 63 64 void 65 ANASTASIS_DB_rollback (void) 66 { 67 struct GNUNET_PQ_ExecuteStatement es[] = { 68 GNUNET_PQ_make_execute ("ROLLBACK"), 69 GNUNET_PQ_EXECUTE_STATEMENT_END 70 }; 71 72 if (GNUNET_OK != 73 GNUNET_PQ_exec_statements (pg->conn, 74 es)) 75 { 76 TALER_LOG_ERROR ("Failed to rollback transaction\n"); 77 GNUNET_break (0); 78 } 79 pg->transaction_name = NULL; 80 } 81 82 83 enum GNUNET_DB_QueryStatus 84 ANASTASIS_DB_commit (void) 85 { 86 enum GNUNET_DB_QueryStatus qs; 87 struct GNUNET_PQ_QueryParam no_params[] = { 88 GNUNET_PQ_query_param_end 89 }; 90 91 PREPARE ("do_commit", 92 "COMMIT"); 93 { 94 PGresult *result; 95 96 result = GNUNET_PQ_exec_prepared (pg->conn, 97 "do_commit", 98 no_params); 99 qs = GNUNET_PQ_eval_result (pg->conn, 100 "do_commit", 101 result); 102 /* PostgreSQL accepts COMMIT on a transaction it has already aborted: it 103 silently rolls the transaction back and answers PGRES_COMMAND_OK with the 104 command tag ROLLBACK. GNUNET_PQ_eval_result() cannot tell that apart from 105 a real commit, so every caller took the success path for a transaction 106 whose writes are gone. Report it as a soft error instead: sessions run 107 SERIALIZABLE, so the overwhelmingly likely cause is an unhandled 108 40001/40P01 for which retrying is right; a caller with a retry loop gives 109 up after MAX_RETRIES and fails visibly, one without fails immediately. 110 111 The *success* path deliberately keeps returning exactly what 112 GNUNET_PQ_eval_prepared_non_select() returned before. That function is 113 GNUNET_PQ_exec_prepared() + GNUNET_PQ_eval_result() plus a 114 strtol(PQcmdTuples()) step that only runs when the status is already 115 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS, and PQcmdTuples() on a COMMIT command 116 tag is the empty string -- so that step yields 0, which is 117 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS again. */ 118 if ( (0 <= qs) && 119 (NULL != result) && 120 (0 == strcmp ("ROLLBACK", 121 PQcmdStatus (result))) ) 122 { 123 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 124 "Transaction `%s' was rolled back by the database instead of committed; a previous statement must have failed without being handled\n", 125 pg->transaction_name); 126 qs = GNUNET_DB_STATUS_SOFT_ERROR; 127 } 128 PQclear (result); 129 } 130 pg->transaction_name = NULL; 131 return qs; 132 } 133 134 135 struct GNUNET_DB_EventHandler * 136 ANASTASIS_DB_event_listen (const struct GNUNET_DB_EventHeaderP *es, 137 struct GNUNET_TIME_Relative timeout, 138 GNUNET_DB_EventCallback cb, 139 void *cb_cls) 140 { 141 return GNUNET_PQ_event_listen (pg->conn, 142 es, 143 timeout, 144 cb, 145 cb_cls); 146 } 147 148 149 void 150 ANASTASIS_DB_event_listen_cancel (struct GNUNET_DB_EventHandler *eh) 151 { 152 GNUNET_PQ_event_listen_cancel (eh); 153 } 154 155 156 void 157 ANASTASIS_DB_event_notify (const struct GNUNET_DB_EventHeaderP *es, 158 const void *extra, 159 size_t extra_size) 160 { 161 GNUNET_PQ_event_notify (pg->conn, 162 es, 163 extra, 164 extra_size); 165 } 166 167 168 /** 169 * Function called each time we connect or reconnect to the 170 * database. Gives the application a chance to run some 171 * per-connection initialization logic. 172 * 173 * @param cls unused, NULL 174 * @param pq database connection handle 175 */ 176 static void 177 reconnect_cb (void *cls, 178 struct GNUNET_PQ_Context *pq) 179 { 180 #if AUTO_EXPLAIN 181 /* Enable verbose logging to see where queries do not 182 properly use indices */ 183 struct GNUNET_PQ_ExecuteStatement es[] = { 184 GNUNET_PQ_make_try_execute ("LOAD 'auto_explain';"), 185 GNUNET_PQ_make_try_execute ("SET auto_explain.log_min_duration=50;"), 186 GNUNET_PQ_make_try_execute ("SET auto_explain.log_timing=TRUE;"), 187 GNUNET_PQ_make_try_execute ("SET auto_explain.log_analyze=TRUE;"), 188 /* https://wiki.postgresql.org/wiki/Serializable suggests to really 189 force the default to 'serializable' if SSI is to be used. */ 190 GNUNET_PQ_make_try_execute ( 191 "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"), 192 GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"), 193 GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"), 194 GNUNET_PQ_make_execute ("SET search_path TO anastasis;"), 195 GNUNET_PQ_EXECUTE_STATEMENT_END 196 }; 197 #else 198 struct GNUNET_PQ_ExecuteStatement es[] = { 199 GNUNET_PQ_make_execute ("SET search_path TO anastasis;"), 200 GNUNET_PQ_EXECUTE_STATEMENT_END 201 }; 202 #endif 203 204 (void) cls; 205 if (GNUNET_OK != 206 GNUNET_PQ_exec_statements (pq, 207 es)) 208 { 209 GNUNET_break (0); 210 return; 211 } 212 ANASTASIS_DB_prep_gen_++; 213 } 214 215 216 /** 217 * Initialize the database connection. 218 * 219 * @param cfg configuration to use 220 * @param check_current true to check if the database schema is current 221 * @return NULL on failure 222 */ 223 static enum GNUNET_GenericReturnValue 224 do_init (const struct GNUNET_CONFIGURATION_Handle *cfg, 225 bool check_current) 226 { 227 pg = GNUNET_new (struct PostgresClosure); 228 pg->cfg = cfg; 229 if (GNUNET_OK != 230 GNUNET_CONFIGURATION_get_value_string (cfg, 231 "anastasis", 232 "CURRENCY", 233 &pg->currency)) 234 { 235 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 236 "anastasis", 237 "CURRENCY"); 238 GNUNET_free (pg); 239 return GNUNET_SYSERR; 240 } 241 pg->conn = GNUNET_PQ_init (pg->cfg, 242 "stasis-postgres", 243 &reconnect_cb, 244 pg); 245 if (NULL == pg->conn) 246 { 247 GNUNET_break (0); 248 ANASTASIS_DB_fini (); 249 return GNUNET_SYSERR; 250 } 251 if (check_current && 252 (GNUNET_OK != 253 GNUNET_PQ_check_current (pg->conn, 254 "stasis-")) ) 255 { 256 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 257 "Database schema is not up-to-date. Try running anastasis-dbinit or anastasis-dbconfig!\n"); 258 ANASTASIS_DB_fini (); 259 return GNUNET_NO; 260 } 261 return GNUNET_OK; 262 } 263 264 265 enum GNUNET_GenericReturnValue 266 ANASTASIS_DB_init (const struct GNUNET_CONFIGURATION_Handle *cfg) 267 { 268 return do_init (cfg, 269 true); 270 } 271 272 273 enum GNUNET_GenericReturnValue 274 ANASTASIS_DB_init_admin (const struct GNUNET_CONFIGURATION_Handle *cfg) 275 { 276 return do_init (cfg, 277 false); 278 } 279 280 281 void 282 ANASTASIS_DB_fini (void) 283 { 284 if (NULL == pg) 285 return; 286 if (NULL != pg->conn) 287 GNUNET_PQ_disconnect (pg->conn); 288 GNUNET_free (pg->currency); 289 GNUNET_free (pg); 290 } 291 292 293 /* end of anastasis-db_pg.c */