1/* trail.c : backing out of aborted Berkeley DB transactions 2 * 3 * ==================================================================== 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 * ==================================================================== 21 */ 22 23#define SVN_WANT_BDB 24#include "svn_private_config.h" 25 26#include <apr_pools.h> 27#include "svn_pools.h" 28#include "svn_fs.h" 29#include "fs.h" 30#include "err.h" 31#include "bdb/bdb-err.h" 32#include "bdb/bdb_compat.h" 33#include "trail.h" 34#include "../libsvn_fs/fs-loader.h" 35 36 37#if defined(SVN_FS__TRAIL_DEBUG) 38 39struct trail_debug_t 40{ 41 struct trail_debug_t *prev; 42 const char *table; 43 const char *op; 44}; 45 46void 47svn_fs_base__trail_debug(trail_t *trail, const char *table, const char *op) 48{ 49 struct trail_debug_t *trail_debug; 50 51 trail_debug = apr_palloc(trail->pool, sizeof(*trail_debug)); 52 trail_debug->prev = trail->trail_debug; 53 trail_debug->table = table; 54 trail_debug->op = op; 55 trail->trail_debug = trail_debug; 56} 57 58static void 59print_trail_debug(trail_t *trail, 60 const char *txn_body_fn_name, 61 const char *filename, int line) 62{ 63 struct trail_debug_t *trail_debug; 64 65 fprintf(stderr, "(%s, %s, %u, %u): ", 66 txn_body_fn_name, filename, line, trail->db_txn ? 1 : 0); 67 68 trail_debug = trail->trail_debug; 69 while (trail_debug) 70 { 71 fprintf(stderr, "(%s, %s) ", trail_debug->table, trail_debug->op); 72 trail_debug = trail_debug->prev; 73 } 74 fprintf(stderr, "\n"); 75} 76#else 77#define print_trail_debug(trail, txn_body_fn_name, filename, line) 78#endif /* defined(SVN_FS__TRAIL_DEBUG) */ 79 80 81static svn_error_t * 82begin_trail(trail_t **trail_p, 83 svn_fs_t *fs, 84 svn_boolean_t use_txn, 85 apr_pool_t *pool) 86{ 87 base_fs_data_t *bfd = fs->fsap_data; 88 trail_t *trail = apr_pcalloc(pool, sizeof(*trail)); 89 90 trail->pool = svn_pool_create(pool); 91 trail->fs = fs; 92 if (use_txn) 93 { 94 /* [*] 95 If we're already inside a trail operation, abort() -- this is 96 a coding problem (and will likely hang the repository anyway). */ 97 SVN_ERR_ASSERT(! bfd->in_txn_trail); 98 99 SVN_ERR(BDB_WRAP(fs, N_("beginning Berkeley DB transaction"), 100 bfd->bdb->env->txn_begin(bfd->bdb->env, 0, 101 &trail->db_txn, 0))); 102 bfd->in_txn_trail = TRUE; 103 } 104 else 105 { 106 trail->db_txn = NULL; 107 } 108 109 *trail_p = trail; 110 return SVN_NO_ERROR; 111} 112 113 114static svn_error_t * 115abort_trail(trail_t *trail) 116{ 117 svn_fs_t *fs = trail->fs; 118 base_fs_data_t *bfd = fs->fsap_data; 119 120 if (trail->db_txn) 121 { 122 /* [**] 123 We have to reset the in_txn_trail flag *before* calling 124 DB_TXN->abort(). If we did it the other way around, the next 125 call to begin_trail() (e.g., as part of a txn retry) would 126 cause an abort, even though there's strictly speaking no 127 programming error involved (see comment [*] above). 128 129 In any case, if aborting the txn fails, restarting it will 130 most likely fail for the same reason, and so it's better to 131 see the returned error than to abort. An obvious example is 132 when DB_TXN->abort() returns DB_RUNRECOVERY. */ 133 bfd->in_txn_trail = FALSE; 134 SVN_ERR(BDB_WRAP(fs, N_("aborting Berkeley DB transaction"), 135 trail->db_txn->abort(trail->db_txn))); 136 } 137 svn_pool_destroy(trail->pool); 138 139 return SVN_NO_ERROR; 140} 141 142 143static svn_error_t * 144commit_trail(trail_t *trail) 145{ 146 int db_err; 147 svn_fs_t *fs = trail->fs; 148 base_fs_data_t *bfd = fs->fsap_data; 149 150 /* According to the example in the Berkeley DB manual, txn_commit 151 doesn't return DB_LOCK_DEADLOCK --- all deadlocks are reported 152 earlier. */ 153 if (trail->db_txn) 154 { 155 /* See comment [**] in abort_trail() above. 156 An error during txn commit will abort the transaction anyway. */ 157 bfd->in_txn_trail = FALSE; 158 SVN_ERR(BDB_WRAP(fs, N_("committing Berkeley DB transaction"), 159 trail->db_txn->commit(trail->db_txn, 0))); 160 } 161 162 /* Do a checkpoint here, if enough has gone on. 163 The checkpoint parameters below are pretty arbitrary. Perhaps 164 there should be an svn_fs_berkeley_mumble function to set them. */ 165 db_err = bfd->bdb->env->txn_checkpoint(bfd->bdb->env, 1024, 5, 0); 166 167 /* Pre-4.1 Berkeley documentation says: 168 169 The DB_ENV->txn_checkpoint function returns a non-zero error 170 value on failure, 0 on success, and returns DB_INCOMPLETE if 171 there were pages that needed to be written to complete the 172 checkpoint but that DB_ENV->memp_sync was unable to write 173 immediately. 174 175 It's safe to ignore DB_INCOMPLETE if we get it while 176 checkpointing. (Post-4.1 Berkeley doesn't have DB_INCOMPLETE 177 anymore, so it's not an issue there.) */ 178 if (db_err) 179 { 180#if SVN_BDB_HAS_DB_INCOMPLETE 181 if (db_err != DB_INCOMPLETE) 182#endif /* SVN_BDB_HAS_DB_INCOMPLETE */ 183 { 184 return svn_fs_bdb__wrap_db 185 (fs, "checkpointing after Berkeley DB transaction", db_err); 186 } 187 } 188 189 return SVN_NO_ERROR; 190} 191 192 193static svn_error_t * 194do_retry(svn_fs_t *fs, 195 svn_error_t *(*txn_body)(void *baton, trail_t *trail), 196 void *baton, 197 svn_boolean_t use_txn, 198 svn_boolean_t destroy_trail_pool, 199 apr_pool_t *pool, 200 const char *txn_body_fn_name, 201 const char *filename, 202 int line) 203{ 204 for (;;) 205 { 206 trail_t *trail; 207 svn_error_t *svn_err, *err; 208 svn_boolean_t deadlocked = FALSE; 209 210 SVN_ERR(begin_trail(&trail, fs, use_txn, pool)); 211 212 /* Do the body of the transaction. */ 213 svn_err = (*txn_body)(baton, trail); 214 215 if (! svn_err) 216 { 217 /* The transaction succeeded! Commit it. */ 218 SVN_ERR(commit_trail(trail)); 219 220 if (use_txn) 221 print_trail_debug(trail, txn_body_fn_name, filename, line); 222 223 /* If our caller doesn't want us to keep trail memory 224 around, destroy our subpool. */ 225 if (destroy_trail_pool) 226 svn_pool_destroy(trail->pool); 227 228 return SVN_NO_ERROR; 229 } 230 231 /* Search for a deadlock error on the stack. */ 232 for (err = svn_err; err; err = err->child) 233 if (err->apr_err == SVN_ERR_FS_BERKELEY_DB_DEADLOCK) 234 deadlocked = TRUE; 235 236 /* Is this a real error, or do we just need to retry? */ 237 if (! deadlocked) 238 { 239 /* Ignore any error returns. The first error is more valuable. */ 240 svn_error_clear(abort_trail(trail)); 241 return svn_err; 242 } 243 244 svn_error_clear(svn_err); 245 246 /* We deadlocked. Abort the transaction, and try again. */ 247 SVN_ERR(abort_trail(trail)); 248 } 249} 250 251 252svn_error_t * 253svn_fs_base__retry_debug(svn_fs_t *fs, 254 svn_error_t *(*txn_body)(void *baton, trail_t *trail), 255 void *baton, 256 svn_boolean_t destroy_trail_pool, 257 apr_pool_t *pool, 258 const char *txn_body_fn_name, 259 const char *filename, 260 int line) 261{ 262 return do_retry(fs, txn_body, baton, TRUE, destroy_trail_pool, pool, 263 txn_body_fn_name, filename, line); 264} 265 266 267#if defined(SVN_FS__TRAIL_DEBUG) 268#undef svn_fs_base__retry_txn 269#endif 270 271svn_error_t * 272svn_fs_base__retry_txn(svn_fs_t *fs, 273 svn_error_t *(*txn_body)(void *baton, trail_t *trail), 274 void *baton, 275 svn_boolean_t destroy_trail_pool, 276 apr_pool_t *pool) 277{ 278 return do_retry(fs, txn_body, baton, TRUE, destroy_trail_pool, pool, 279 "unknown", "", 0); 280} 281 282 283svn_error_t * 284svn_fs_base__retry(svn_fs_t *fs, 285 svn_error_t *(*txn_body)(void *baton, trail_t *trail), 286 void *baton, 287 svn_boolean_t destroy_trail_pool, 288 apr_pool_t *pool) 289{ 290 return do_retry(fs, txn_body, baton, FALSE, destroy_trail_pool, pool, 291 NULL, NULL, 0); 292} 293