1251881Speter/* trail.h : internal interface to backing out of aborted Berkeley DB txns
2251881Speter *
3251881Speter * ====================================================================
4251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
5251881Speter *    or more contributor license agreements.  See the NOTICE file
6251881Speter *    distributed with this work for additional information
7251881Speter *    regarding copyright ownership.  The ASF licenses this file
8251881Speter *    to you under the Apache License, Version 2.0 (the
9251881Speter *    "License"); you may not use this file except in compliance
10251881Speter *    with the License.  You may obtain a copy of the License at
11251881Speter *
12251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
13251881Speter *
14251881Speter *    Unless required by applicable law or agreed to in writing,
15251881Speter *    software distributed under the License is distributed on an
16251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17251881Speter *    KIND, either express or implied.  See the License for the
18251881Speter *    specific language governing permissions and limitations
19251881Speter *    under the License.
20251881Speter * ====================================================================
21251881Speter */
22251881Speter
23251881Speter#ifndef SVN_LIBSVN_FS_TRAIL_H
24251881Speter#define SVN_LIBSVN_FS_TRAIL_H
25251881Speter
26251881Speter#define SVN_WANT_BDB
27251881Speter#include "svn_private_config.h"
28251881Speter
29251881Speter#include <apr_pools.h>
30251881Speter#include "svn_fs.h"
31251881Speter#include "fs.h"
32251881Speter
33251881Speter#ifdef __cplusplus
34251881Speterextern "C" {
35251881Speter#endif /* __cplusplus */
36251881Speter
37251881Speter
38251881Speter/* "How do I get a trail object?  All these functions in the
39251881Speter   filesystem expect them, and I can't find a function that returns
40251881Speter   one."
41251881Speter
42251881Speter   Well, there isn't a function that returns a trail.  All trails come
43251881Speter   from svn_fs_base__retry_txn.  Here's how to use that:
44251881Speter
45251881Speter   When using Berkeley DB transactions to protect the integrity of a
46251881Speter   database, there are several things you need to keep in mind:
47251881Speter
48251881Speter   - Any Berkeley DB operation you perform as part of a Berkeley DB
49251881Speter     transaction may return DB_LOCK_DEADLOCK, meaning that your
50251881Speter     operation interferes with some other transaction in progress.
51251881Speter     When this happens, you must abort the transaction, which undoes
52251881Speter     all the changes you've made so far, and try it again.  So every
53251881Speter     piece of code you ever write to bang on the DB needs to be
54251881Speter     wrapped up in a retry loop.
55251881Speter
56251881Speter   - If, while you're doing your database operations, you also change
57251881Speter     some in-memory data structures, then you may want to revert those
58251881Speter     changes if the transaction deadlocks and needs to be retried.
59251881Speter
60251881Speter   - If you get a `real' error (i.e., something other than
61251881Speter     DB_LOCK_DEADLOCK), you must abort your DB transaction, to release
62251881Speter     its locks and return the database to its previous state.
63251881Speter     Similarly, you may want to unroll some changes you've made to
64251881Speter     in-memory data structures.
65251881Speter
66251881Speter   - Since a transaction insulates you from database changes made by
67251881Speter     other processes, it's often possible to cache information about
68251881Speter     database contents while the transaction lasts.  However, this
69251881Speter     cache may become stale once your transaction is over.  So you may
70251881Speter     need to clear your cache once the transaction completes, either
71251881Speter     successfully or unsuccessfully.
72251881Speter
73251881Speter   The `svn_fs_base__retry_txn' function and its friends help you manage
74251881Speter   some of that, in one nice package.
75251881Speter
76251881Speter   To use it, write your code in a function like this:
77251881Speter
78251881Speter       static svn_error_t *
79251881Speter       txn_body_do_my_thing (void *baton,
80251881Speter                             trail_t *trail)
81251881Speter       {
82251881Speter         ...
83251881Speter         Do everything which needs to be protected by a Berkeley DB
84251881Speter         transaction here.  Use TRAIL->db_txn as your Berkeley DB
85251881Speter         transaction, and do your allocation in TRAIL->pool.  Pass
86251881Speter         TRAIL on through to any functions which require one.
87251881Speter
88251881Speter         If a Berkeley DB operation returns DB_LOCK_DEADLOCK, just
89251881Speter         return that using the normal Subversion error mechanism
90251881Speter         (using DB_ERR, for example); don't write a retry loop.  If you
91251881Speter         encounter some other kind of error, return it in the normal
92251881Speter         fashion.
93251881Speter         ...
94251881Speter       }
95251881Speter
96251881Speter   Now, call svn_fs_base__retry_txn, passing a pointer to your function as
97251881Speter   an argument:
98251881Speter
99251881Speter       err = svn_fs_base__retry_txn (fs, txn_body_do_my_thing, baton, pool);
100251881Speter
101251881Speter   This will simply invoke your function `txn_body_do_my_thing',
102251881Speter   passing BATON through unchanged, and providing a fresh TRAIL
103251881Speter   object, containing a pointer to the filesystem object, a Berkeley
104251881Speter   DB transaction and an APR pool -- a subpool of POOL -- you should
105251881Speter   use.
106251881Speter
107251881Speter   If your function returns a Subversion error wrapping a Berkeley DB
108251881Speter   DB_LOCK_DEADLOCK error, `svn_fs_base__retry_txn' will abort the trail's
109251881Speter   Berkeley DB transaction for you (thus undoing any database changes
110251881Speter   you've made), free the trail's subpool (thus undoing any allocation
111251881Speter   you may have done), and try the whole thing again with a new trail,
112251881Speter   containing a new Berkeley DB transaction and pool.
113251881Speter
114251881Speter   If your function returns any other kind of Subversion error,
115251881Speter   `svn_fs_base__retry_txn' will abort the trail's Berkeley DB transaction,
116251881Speter   free the subpool, and return your error to its caller.
117251881Speter
118251881Speter   If, heavens forbid, your function actually succeeds, returning
119251881Speter   SVN_NO_ERROR, `svn_fs_base__retry_txn' commits the trail's Berkeley DB
120251881Speter   transaction, thus making your DB changes permanent, leaves the
121251881Speter   trail's pool alone so all the objects it contains are still
122251881Speter   around (unless you request otherwise), and returns SVN_NO_ERROR.
123251881Speter
124251881Speter
125251881Speter   Keep the amount of work done in a trail small. C-Mike Pilato said to me:
126251881Speter
127251881Speter   I want to draw your attention to something that you may or may not realize
128251881Speter   about designing for the BDB backend.  The 'trail' objects are (generally)
129251881Speter   representative of Berkeley DB transactions -- that part I'm sure you know.
130251881Speter   But you might not realize the value of keeping transactions as small as
131251881Speter   possible.  Berkeley DB will accumulate locks (which I believe are
132251881Speter   page-level, not as tight as row-level like you might hope) over the course
133251881Speter   of a transaction, releasing those locks only at transaction commit/abort.
134251881Speter   Berkeley DB backends are configured to have a maximum number of locks and
135251881Speter   lockers allowed, and it's easier than you might think to hit the max-locks
136251881Speter   thresholds (especially under high concurrency) and see an error (typically a
137251881Speter   "Cannot allocate memory") result from that.
138251881Speter
139251881Speter   For example, in [a loop] you are writing a bunch of rows to the
140251881Speter   `changes' table.  Could be 10.  Could be 100,000.  100,000 writes and
141251881Speter   associated locks might be a problem or it might not.  But I use it as a way
142251881Speter   to encourage you to think about reducing the amount of work you spend in any
143251881Speter   one trail [...].
144251881Speter*/
145251881Speter
146251881Speterstruct trail_t
147251881Speter{
148251881Speter  /* A Berkeley DB transaction.  */
149251881Speter  DB_TXN *db_txn;
150251881Speter
151251881Speter  /* The filesystem object with which this trail is associated. */
152251881Speter  svn_fs_t *fs;
153251881Speter
154251881Speter  /* A pool to allocate things in as part of that transaction --- a
155251881Speter     subpool of the one passed to `begin_trail'.  We destroy this pool
156251881Speter     if we abort the transaction, and leave it around otherwise.  */
157251881Speter  apr_pool_t *pool;
158251881Speter
159251881Speter#if defined(SVN_FS__TRAIL_DEBUG)
160251881Speter  struct trail_debug_t *trail_debug;
161251881Speter#endif
162251881Speter};
163251881Spetertypedef struct trail_t trail_t;
164251881Speter
165251881Speter
166251881Speter/* Try a Berkeley DB transaction repeatedly until it doesn't deadlock.
167251881Speter
168251881Speter   That is:
169251881Speter   - Begin a new Berkeley DB transaction, DB_TXN, in the filesystem FS.
170251881Speter   - Allocate a subpool of POOL, TXN_POOL.
171251881Speter   - Start a new trail, TRAIL, pointing to DB_TXN and TXN_POOL.
172251881Speter   - Apply TXN_BODY to BATON and TRAIL.  TXN_BODY should try to do
173251881Speter     some series of DB operations which needs to be atomic, using
174251881Speter     TRAIL->db_txn as the transaction, and TRAIL->pool for allocation.
175251881Speter     If a DB operation deadlocks, or if any other kind of error
176251881Speter     happens, TXN_BODY should simply return with an appropriate
177251881Speter     svn_error_t, E.
178251881Speter   - If TXN_BODY returns SVN_NO_ERROR, then commit the transaction,
179251881Speter     run any completion functions, and return SVN_NO_ERROR.  Do *not*
180251881Speter     free TXN_POOL (unless DESTROY_TRAIL_POOL is set).
181251881Speter   - If E is a Berkeley DB error indicating that a deadlock occurred,
182251881Speter     abort the DB transaction and free TXN_POOL.  Then retry the whole
183251881Speter     thing from the top.
184251881Speter   - If E is any other kind of error, free TXN_POOL and return E.
185251881Speter
186251881Speter   One benefit of using this function is that it makes it easy to
187251881Speter   ensure that whatever transactions a filesystem function starts, it
188251881Speter   either aborts or commits before it returns.  If we don't somehow
189251881Speter   complete all our transactions, later operations could deadlock.  */
190251881Spetersvn_error_t *
191251881Spetersvn_fs_base__retry_txn(svn_fs_t *fs,
192251881Speter                       svn_error_t *(*txn_body)(void *baton,
193251881Speter                                                trail_t *trail),
194251881Speter                       void *baton,
195251881Speter                       svn_boolean_t destroy_trail_pool,
196251881Speter                       apr_pool_t *pool);
197251881Speter
198251881Spetersvn_error_t *
199251881Spetersvn_fs_base__retry_debug(svn_fs_t *fs,
200251881Speter                         svn_error_t *(*txn_body)(void *baton,
201251881Speter                                                  trail_t *trail),
202251881Speter                         void *baton,
203251881Speter                         svn_boolean_t destroy_trail_pool,
204251881Speter                         apr_pool_t *pool,
205251881Speter                         const char *txn_body_fn_name,
206251881Speter                         const char *filename,
207251881Speter                         int line);
208251881Speter
209251881Speter#if defined(SVN_FS__TRAIL_DEBUG)
210251881Speter#define svn_fs_base__retry_txn(fs, txn_body, baton, destroy, pool) \
211251881Speter  svn_fs_base__retry_debug(fs, txn_body, baton, destroy, pool,     \
212251881Speter                           #txn_body, __FILE__, __LINE__)
213251881Speter#endif
214251881Speter
215251881Speter
216251881Speter/* Try an action repeatedly until it doesn't deadlock.  This is
217251881Speter   exactly like svn_fs_base__retry_txn() (whose documentation you really
218251881Speter   should read) except that no Berkeley DB transaction is created. */
219251881Spetersvn_error_t *svn_fs_base__retry(svn_fs_t *fs,
220251881Speter                                svn_error_t *(*txn_body)(void *baton,
221251881Speter                                                         trail_t *trail),
222251881Speter                                void *baton,
223251881Speter                                svn_boolean_t destroy_trail_pool,
224251881Speter                                apr_pool_t *pool);
225251881Speter
226251881Speter
227251881Speter/* Record that OPeration is being done on TABLE in the TRAIL. */
228251881Speter#if defined(SVN_FS__TRAIL_DEBUG)
229251881Spetervoid svn_fs_base__trail_debug(trail_t *trail, const char *table,
230251881Speter                              const char *op);
231251881Speter#else
232251881Speter#define svn_fs_base__trail_debug(trail, table, operation)
233251881Speter#endif
234251881Speter
235251881Speter#ifdef __cplusplus
236251881Speter}
237251881Speter#endif /* __cplusplus */
238251881Speter
239251881Speter#endif /* SVN_LIBSVN_FS_TRAIL_H */
240