1/* trail.h : internal interface to backing out of aborted Berkeley DB txns 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#ifndef SVN_LIBSVN_FS_TRAIL_H 24#define SVN_LIBSVN_FS_TRAIL_H 25 26#define SVN_WANT_BDB 27#include "svn_private_config.h" 28 29#include <apr_pools.h> 30#include "svn_fs.h" 31#include "fs.h" 32 33#ifdef __cplusplus 34extern "C" { 35#endif /* __cplusplus */ 36 37 38/* "How do I get a trail object? All these functions in the 39 filesystem expect them, and I can't find a function that returns 40 one." 41 42 Well, there isn't a function that returns a trail. All trails come 43 from svn_fs_base__retry_txn. Here's how to use that: 44 45 When using Berkeley DB transactions to protect the integrity of a 46 database, there are several things you need to keep in mind: 47 48 - Any Berkeley DB operation you perform as part of a Berkeley DB 49 transaction may return DB_LOCK_DEADLOCK, meaning that your 50 operation interferes with some other transaction in progress. 51 When this happens, you must abort the transaction, which undoes 52 all the changes you've made so far, and try it again. So every 53 piece of code you ever write to bang on the DB needs to be 54 wrapped up in a retry loop. 55 56 - If, while you're doing your database operations, you also change 57 some in-memory data structures, then you may want to revert those 58 changes if the transaction deadlocks and needs to be retried. 59 60 - If you get a `real' error (i.e., something other than 61 DB_LOCK_DEADLOCK), you must abort your DB transaction, to release 62 its locks and return the database to its previous state. 63 Similarly, you may want to unroll some changes you've made to 64 in-memory data structures. 65 66 - Since a transaction insulates you from database changes made by 67 other processes, it's often possible to cache information about 68 database contents while the transaction lasts. However, this 69 cache may become stale once your transaction is over. So you may 70 need to clear your cache once the transaction completes, either 71 successfully or unsuccessfully. 72 73 The `svn_fs_base__retry_txn' function and its friends help you manage 74 some of that, in one nice package. 75 76 To use it, write your code in a function like this: 77 78 static svn_error_t * 79 txn_body_do_my_thing (void *baton, 80 trail_t *trail) 81 { 82 ... 83 Do everything which needs to be protected by a Berkeley DB 84 transaction here. Use TRAIL->db_txn as your Berkeley DB 85 transaction, and do your allocation in TRAIL->pool. Pass 86 TRAIL on through to any functions which require one. 87 88 If a Berkeley DB operation returns DB_LOCK_DEADLOCK, just 89 return that using the normal Subversion error mechanism 90 (using DB_ERR, for example); don't write a retry loop. If you 91 encounter some other kind of error, return it in the normal 92 fashion. 93 ... 94 } 95 96 Now, call svn_fs_base__retry_txn, passing a pointer to your function as 97 an argument: 98 99 err = svn_fs_base__retry_txn (fs, txn_body_do_my_thing, baton, pool); 100 101 This will simply invoke your function `txn_body_do_my_thing', 102 passing BATON through unchanged, and providing a fresh TRAIL 103 object, containing a pointer to the filesystem object, a Berkeley 104 DB transaction and an APR pool -- a subpool of POOL -- you should 105 use. 106 107 If your function returns a Subversion error wrapping a Berkeley DB 108 DB_LOCK_DEADLOCK error, `svn_fs_base__retry_txn' will abort the trail's 109 Berkeley DB transaction for you (thus undoing any database changes 110 you've made), free the trail's subpool (thus undoing any allocation 111 you may have done), and try the whole thing again with a new trail, 112 containing a new Berkeley DB transaction and pool. 113 114 If your function returns any other kind of Subversion error, 115 `svn_fs_base__retry_txn' will abort the trail's Berkeley DB transaction, 116 free the subpool, and return your error to its caller. 117 118 If, heavens forbid, your function actually succeeds, returning 119 SVN_NO_ERROR, `svn_fs_base__retry_txn' commits the trail's Berkeley DB 120 transaction, thus making your DB changes permanent, leaves the 121 trail's pool alone so all the objects it contains are still 122 around (unless you request otherwise), and returns SVN_NO_ERROR. 123 124 125 Keep the amount of work done in a trail small. C-Mike Pilato said to me: 126 127 I want to draw your attention to something that you may or may not realize 128 about designing for the BDB backend. The 'trail' objects are (generally) 129 representative of Berkeley DB transactions -- that part I'm sure you know. 130 But you might not realize the value of keeping transactions as small as 131 possible. Berkeley DB will accumulate locks (which I believe are 132 page-level, not as tight as row-level like you might hope) over the course 133 of a transaction, releasing those locks only at transaction commit/abort. 134 Berkeley DB backends are configured to have a maximum number of locks and 135 lockers allowed, and it's easier than you might think to hit the max-locks 136 thresholds (especially under high concurrency) and see an error (typically a 137 "Cannot allocate memory") result from that. 138 139 For example, in [a loop] you are writing a bunch of rows to the 140 `changes' table. Could be 10. Could be 100,000. 100,000 writes and 141 associated locks might be a problem or it might not. But I use it as a way 142 to encourage you to think about reducing the amount of work you spend in any 143 one trail [...]. 144*/ 145 146struct trail_t 147{ 148 /* A Berkeley DB transaction. */ 149 DB_TXN *db_txn; 150 151 /* The filesystem object with which this trail is associated. */ 152 svn_fs_t *fs; 153 154 /* A pool to allocate things in as part of that transaction --- a 155 subpool of the one passed to `begin_trail'. We destroy this pool 156 if we abort the transaction, and leave it around otherwise. */ 157 apr_pool_t *pool; 158 159#if defined(SVN_FS__TRAIL_DEBUG) 160 struct trail_debug_t *trail_debug; 161#endif 162}; 163typedef struct trail_t trail_t; 164 165 166/* Try a Berkeley DB transaction repeatedly until it doesn't deadlock. 167 168 That is: 169 - Begin a new Berkeley DB transaction, DB_TXN, in the filesystem FS. 170 - Allocate a subpool of POOL, TXN_POOL. 171 - Start a new trail, TRAIL, pointing to DB_TXN and TXN_POOL. 172 - Apply TXN_BODY to BATON and TRAIL. TXN_BODY should try to do 173 some series of DB operations which needs to be atomic, using 174 TRAIL->db_txn as the transaction, and TRAIL->pool for allocation. 175 If a DB operation deadlocks, or if any other kind of error 176 happens, TXN_BODY should simply return with an appropriate 177 svn_error_t, E. 178 - If TXN_BODY returns SVN_NO_ERROR, then commit the transaction, 179 run any completion functions, and return SVN_NO_ERROR. Do *not* 180 free TXN_POOL (unless DESTROY_TRAIL_POOL is set). 181 - If E is a Berkeley DB error indicating that a deadlock occurred, 182 abort the DB transaction and free TXN_POOL. Then retry the whole 183 thing from the top. 184 - If E is any other kind of error, free TXN_POOL and return E. 185 186 One benefit of using this function is that it makes it easy to 187 ensure that whatever transactions a filesystem function starts, it 188 either aborts or commits before it returns. If we don't somehow 189 complete all our transactions, later operations could deadlock. */ 190svn_error_t * 191svn_fs_base__retry_txn(svn_fs_t *fs, 192 svn_error_t *(*txn_body)(void *baton, 193 trail_t *trail), 194 void *baton, 195 svn_boolean_t destroy_trail_pool, 196 apr_pool_t *pool); 197 198svn_error_t * 199svn_fs_base__retry_debug(svn_fs_t *fs, 200 svn_error_t *(*txn_body)(void *baton, 201 trail_t *trail), 202 void *baton, 203 svn_boolean_t destroy_trail_pool, 204 apr_pool_t *pool, 205 const char *txn_body_fn_name, 206 const char *filename, 207 int line); 208 209#if defined(SVN_FS__TRAIL_DEBUG) 210#define svn_fs_base__retry_txn(fs, txn_body, baton, destroy, pool) \ 211 svn_fs_base__retry_debug(fs, txn_body, baton, destroy, pool, \ 212 #txn_body, __FILE__, __LINE__) 213#endif 214 215 216/* Try an action repeatedly until it doesn't deadlock. This is 217 exactly like svn_fs_base__retry_txn() (whose documentation you really 218 should read) except that no Berkeley DB transaction is created. */ 219svn_error_t *svn_fs_base__retry(svn_fs_t *fs, 220 svn_error_t *(*txn_body)(void *baton, 221 trail_t *trail), 222 void *baton, 223 svn_boolean_t destroy_trail_pool, 224 apr_pool_t *pool); 225 226 227/* Record that OPeration is being done on TABLE in the TRAIL. */ 228#if defined(SVN_FS__TRAIL_DEBUG) 229void svn_fs_base__trail_debug(trail_t *trail, const char *table, 230 const char *op); 231#else 232#define svn_fs_base__trail_debug(trail, table, operation) 233#endif 234 235#ifdef __cplusplus 236} 237#endif /* __cplusplus */ 238 239#endif /* SVN_LIBSVN_FS_TRAIL_H */ 240