1 /* trail.h : internal interface to backing out of aborted Berkeley DB txns
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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
20 * ====================================================================
23 #ifndef SVN_LIBSVN_FS_TRAIL_H
24 #define SVN_LIBSVN_FS_TRAIL_H
27 #include "svn_private_config.h"
29 #include <apr_pools.h>
35 #endif /* __cplusplus */
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
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:
45 When using Berkeley DB transactions to protect the integrity of a
46 database, there are several things you need to keep in mind:
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.
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.
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.
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.
73 The `svn_fs_base__retry_txn' function and its friends help you manage
74 some of that, in one nice package.
76 To use it, write your code in a function like this:
79 txn_body_do_my_thing (void *baton,
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.
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
96 Now, call svn_fs_base__retry_txn, passing a pointer to your function as
99 err = svn_fs_base__retry_txn (fs, txn_body_do_my_thing, baton, pool);
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
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.
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.
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.
125 Keep the amount of work done in a trail small. C-Mike Pilato said to me:
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.
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
148 /* A Berkeley DB transaction. */
151 /* The filesystem object with which this trail is associated. */
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. */
159 #if defined(SVN_FS__TRAIL_DEBUG)
160 struct trail_debug_t *trail_debug;
163 typedef struct trail_t trail_t;
166 /* Try a Berkeley DB transaction repeatedly until it doesn't deadlock.
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
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
184 - If E is any other kind of error, free TXN_POOL and return E.
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. */
191 svn_fs_base__retry_txn(svn_fs_t *fs,
192 svn_error_t *(*txn_body)(void *baton,
195 svn_boolean_t destroy_trail_pool,
199 svn_fs_base__retry_debug(svn_fs_t *fs,
200 svn_error_t *(*txn_body)(void *baton,
203 svn_boolean_t destroy_trail_pool,
205 const char *txn_body_fn_name,
206 const char *filename,
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__)
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. */
219 svn_error_t *svn_fs_base__retry(svn_fs_t *fs,
220 svn_error_t *(*txn_body)(void *baton,
223 svn_boolean_t destroy_trail_pool,
227 /* Record that OPeration is being done on TABLE in the TRAIL. */
228 #if defined(SVN_FS__TRAIL_DEBUG)
229 void svn_fs_base__trail_debug(trail_t *trail, const char *table,
232 #define svn_fs_base__trail_debug(trail, table, operation)
237 #endif /* __cplusplus */
239 #endif /* SVN_LIBSVN_FS_TRAIL_H */