1251881Speter/* trail.c : backing out of aborted Berkeley DB transactions
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#define SVN_WANT_BDB
24251881Speter#include "svn_private_config.h"
25251881Speter
26251881Speter#include <apr_pools.h>
27251881Speter#include "svn_pools.h"
28251881Speter#include "svn_fs.h"
29251881Speter#include "fs.h"
30251881Speter#include "err.h"
31251881Speter#include "bdb/bdb-err.h"
32251881Speter#include "bdb/bdb_compat.h"
33251881Speter#include "trail.h"
34251881Speter#include "../libsvn_fs/fs-loader.h"
35251881Speter
36251881Speter
37251881Speter#if defined(SVN_FS__TRAIL_DEBUG)
38251881Speter
39251881Speterstruct trail_debug_t
40251881Speter{
41251881Speter  struct trail_debug_t *prev;
42251881Speter  const char *table;
43251881Speter  const char *op;
44251881Speter};
45251881Speter
46251881Spetervoid
47251881Spetersvn_fs_base__trail_debug(trail_t *trail, const char *table, const char *op)
48251881Speter{
49251881Speter  struct trail_debug_t *trail_debug;
50251881Speter
51251881Speter  trail_debug = apr_palloc(trail->pool, sizeof(*trail_debug));
52251881Speter  trail_debug->prev = trail->trail_debug;
53251881Speter  trail_debug->table = table;
54251881Speter  trail_debug->op = op;
55251881Speter  trail->trail_debug = trail_debug;
56251881Speter}
57251881Speter
58251881Speterstatic void
59251881Speterprint_trail_debug(trail_t *trail,
60251881Speter                  const char *txn_body_fn_name,
61251881Speter                  const char *filename, int line)
62251881Speter{
63251881Speter  struct trail_debug_t *trail_debug;
64251881Speter
65251881Speter  fprintf(stderr, "(%s, %s, %u, %u): ",
66251881Speter          txn_body_fn_name, filename, line, trail->db_txn ? 1 : 0);
67251881Speter
68251881Speter  trail_debug = trail->trail_debug;
69251881Speter  while (trail_debug)
70251881Speter    {
71251881Speter      fprintf(stderr, "(%s, %s) ", trail_debug->table, trail_debug->op);
72251881Speter      trail_debug = trail_debug->prev;
73251881Speter    }
74251881Speter  fprintf(stderr, "\n");
75251881Speter}
76251881Speter#else
77251881Speter#define print_trail_debug(trail, txn_body_fn_name, filename, line)
78251881Speter#endif /* defined(SVN_FS__TRAIL_DEBUG) */
79251881Speter
80251881Speter
81251881Speterstatic svn_error_t *
82251881Speterbegin_trail(trail_t **trail_p,
83251881Speter            svn_fs_t *fs,
84251881Speter            svn_boolean_t use_txn,
85251881Speter            apr_pool_t *pool)
86251881Speter{
87251881Speter  base_fs_data_t *bfd = fs->fsap_data;
88251881Speter  trail_t *trail = apr_pcalloc(pool, sizeof(*trail));
89251881Speter
90251881Speter  trail->pool = svn_pool_create(pool);
91251881Speter  trail->fs = fs;
92251881Speter  if (use_txn)
93251881Speter    {
94251881Speter      /* [*]
95251881Speter         If we're already inside a trail operation, abort() -- this is
96251881Speter         a coding problem (and will likely hang the repository anyway). */
97251881Speter      SVN_ERR_ASSERT(! bfd->in_txn_trail);
98251881Speter
99251881Speter      SVN_ERR(BDB_WRAP(fs, N_("beginning Berkeley DB transaction"),
100251881Speter                       bfd->bdb->env->txn_begin(bfd->bdb->env, 0,
101251881Speter                                                &trail->db_txn, 0)));
102251881Speter      bfd->in_txn_trail = TRUE;
103251881Speter    }
104251881Speter  else
105251881Speter    {
106251881Speter      trail->db_txn = NULL;
107251881Speter    }
108251881Speter
109251881Speter  *trail_p = trail;
110251881Speter  return SVN_NO_ERROR;
111251881Speter}
112251881Speter
113251881Speter
114251881Speterstatic svn_error_t *
115251881Speterabort_trail(trail_t *trail)
116251881Speter{
117251881Speter  svn_fs_t *fs = trail->fs;
118251881Speter  base_fs_data_t *bfd = fs->fsap_data;
119251881Speter
120251881Speter  if (trail->db_txn)
121251881Speter    {
122251881Speter      /* [**]
123251881Speter         We have to reset the in_txn_trail flag *before* calling
124251881Speter         DB_TXN->abort().  If we did it the other way around, the next
125251881Speter         call to begin_trail() (e.g., as part of a txn retry) would
126251881Speter         cause an abort, even though there's strictly speaking no
127251881Speter         programming error involved (see comment [*] above).
128251881Speter
129251881Speter         In any case, if aborting the txn fails, restarting it will
130251881Speter         most likely fail for the same reason, and so it's better to
131251881Speter         see the returned error than to abort.  An obvious example is
132251881Speter         when DB_TXN->abort() returns DB_RUNRECOVERY. */
133251881Speter      bfd->in_txn_trail = FALSE;
134251881Speter      SVN_ERR(BDB_WRAP(fs, N_("aborting Berkeley DB transaction"),
135251881Speter                       trail->db_txn->abort(trail->db_txn)));
136251881Speter    }
137251881Speter  svn_pool_destroy(trail->pool);
138251881Speter
139251881Speter  return SVN_NO_ERROR;
140251881Speter}
141251881Speter
142251881Speter
143251881Speterstatic svn_error_t *
144251881Spetercommit_trail(trail_t *trail)
145251881Speter{
146251881Speter  int db_err;
147251881Speter  svn_fs_t *fs = trail->fs;
148251881Speter  base_fs_data_t *bfd = fs->fsap_data;
149251881Speter
150251881Speter  /* According to the example in the Berkeley DB manual, txn_commit
151251881Speter     doesn't return DB_LOCK_DEADLOCK --- all deadlocks are reported
152251881Speter     earlier.  */
153251881Speter  if (trail->db_txn)
154251881Speter    {
155251881Speter      /* See comment [**] in abort_trail() above.
156251881Speter         An error during txn commit will abort the transaction anyway. */
157251881Speter      bfd->in_txn_trail = FALSE;
158251881Speter      SVN_ERR(BDB_WRAP(fs, N_("committing Berkeley DB transaction"),
159251881Speter                       trail->db_txn->commit(trail->db_txn, 0)));
160251881Speter    }
161251881Speter
162251881Speter  /* Do a checkpoint here, if enough has gone on.
163251881Speter     The checkpoint parameters below are pretty arbitrary.  Perhaps
164251881Speter     there should be an svn_fs_berkeley_mumble function to set them.  */
165251881Speter  db_err = bfd->bdb->env->txn_checkpoint(bfd->bdb->env, 1024, 5, 0);
166251881Speter
167251881Speter  /* Pre-4.1 Berkeley documentation says:
168251881Speter
169251881Speter        The DB_ENV->txn_checkpoint function returns a non-zero error
170251881Speter        value on failure, 0 on success, and returns DB_INCOMPLETE if
171251881Speter        there were pages that needed to be written to complete the
172251881Speter        checkpoint but that DB_ENV->memp_sync was unable to write
173251881Speter        immediately.
174251881Speter
175251881Speter     It's safe to ignore DB_INCOMPLETE if we get it while
176251881Speter     checkpointing.  (Post-4.1 Berkeley doesn't have DB_INCOMPLETE
177251881Speter     anymore, so it's not an issue there.)  */
178251881Speter  if (db_err)
179251881Speter    {
180251881Speter#if SVN_BDB_HAS_DB_INCOMPLETE
181251881Speter      if (db_err != DB_INCOMPLETE)
182251881Speter#endif /* SVN_BDB_HAS_DB_INCOMPLETE */
183251881Speter        {
184251881Speter          return svn_fs_bdb__wrap_db
185251881Speter            (fs, "checkpointing after Berkeley DB transaction", db_err);
186251881Speter        }
187251881Speter    }
188251881Speter
189251881Speter  return SVN_NO_ERROR;
190251881Speter}
191251881Speter
192251881Speter
193251881Speterstatic svn_error_t *
194251881Speterdo_retry(svn_fs_t *fs,
195251881Speter         svn_error_t *(*txn_body)(void *baton, trail_t *trail),
196251881Speter         void *baton,
197251881Speter         svn_boolean_t use_txn,
198251881Speter         svn_boolean_t destroy_trail_pool,
199251881Speter         apr_pool_t *pool,
200251881Speter         const char *txn_body_fn_name,
201251881Speter         const char *filename,
202251881Speter         int line)
203251881Speter{
204251881Speter  for (;;)
205251881Speter    {
206251881Speter      trail_t *trail;
207251881Speter      svn_error_t *svn_err, *err;
208251881Speter      svn_boolean_t deadlocked = FALSE;
209251881Speter
210251881Speter      SVN_ERR(begin_trail(&trail, fs, use_txn, pool));
211251881Speter
212251881Speter      /* Do the body of the transaction.  */
213251881Speter      svn_err = (*txn_body)(baton, trail);
214251881Speter
215251881Speter      if (! svn_err)
216251881Speter        {
217251881Speter          /* The transaction succeeded!  Commit it.  */
218251881Speter          SVN_ERR(commit_trail(trail));
219251881Speter
220251881Speter          if (use_txn)
221251881Speter            print_trail_debug(trail, txn_body_fn_name, filename, line);
222251881Speter
223251881Speter          /* If our caller doesn't want us to keep trail memory
224251881Speter             around, destroy our subpool. */
225251881Speter          if (destroy_trail_pool)
226251881Speter            svn_pool_destroy(trail->pool);
227251881Speter
228251881Speter          return SVN_NO_ERROR;
229251881Speter        }
230251881Speter
231251881Speter      /* Search for a deadlock error on the stack. */
232251881Speter      for (err = svn_err; err; err = err->child)
233251881Speter        if (err->apr_err == SVN_ERR_FS_BERKELEY_DB_DEADLOCK)
234251881Speter          deadlocked = TRUE;
235251881Speter
236251881Speter      /* Is this a real error, or do we just need to retry?  */
237251881Speter      if (! deadlocked)
238251881Speter        {
239251881Speter          /* Ignore any error returns.  The first error is more valuable.  */
240251881Speter          svn_error_clear(abort_trail(trail));
241251881Speter          return svn_err;
242251881Speter        }
243251881Speter
244251881Speter      svn_error_clear(svn_err);
245251881Speter
246251881Speter      /* We deadlocked.  Abort the transaction, and try again.  */
247251881Speter      SVN_ERR(abort_trail(trail));
248251881Speter    }
249251881Speter}
250251881Speter
251251881Speter
252251881Spetersvn_error_t *
253251881Spetersvn_fs_base__retry_debug(svn_fs_t *fs,
254251881Speter                         svn_error_t *(*txn_body)(void *baton, trail_t *trail),
255251881Speter                         void *baton,
256251881Speter                         svn_boolean_t destroy_trail_pool,
257251881Speter                         apr_pool_t *pool,
258251881Speter                         const char *txn_body_fn_name,
259251881Speter                         const char *filename,
260251881Speter                         int line)
261251881Speter{
262251881Speter  return do_retry(fs, txn_body, baton, TRUE, destroy_trail_pool, pool,
263251881Speter                  txn_body_fn_name, filename, line);
264251881Speter}
265251881Speter
266251881Speter
267251881Speter#if defined(SVN_FS__TRAIL_DEBUG)
268251881Speter#undef svn_fs_base__retry_txn
269251881Speter#endif
270251881Speter
271251881Spetersvn_error_t *
272251881Spetersvn_fs_base__retry_txn(svn_fs_t *fs,
273251881Speter                       svn_error_t *(*txn_body)(void *baton, trail_t *trail),
274251881Speter                       void *baton,
275251881Speter                       svn_boolean_t destroy_trail_pool,
276251881Speter                       apr_pool_t *pool)
277251881Speter{
278251881Speter  return do_retry(fs, txn_body, baton, TRUE, destroy_trail_pool, pool,
279251881Speter                  "unknown", "", 0);
280251881Speter}
281251881Speter
282251881Speter
283251881Spetersvn_error_t *
284251881Spetersvn_fs_base__retry(svn_fs_t *fs,
285251881Speter                   svn_error_t *(*txn_body)(void *baton, trail_t *trail),
286251881Speter                   void *baton,
287251881Speter                   svn_boolean_t destroy_trail_pool,
288251881Speter                   apr_pool_t *pool)
289251881Speter{
290251881Speter  return do_retry(fs, txn_body, baton, FALSE, destroy_trail_pool, pool,
291251881Speter                  NULL, NULL, 0);
292251881Speter}
293