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