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