]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_fs_base/trail.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_fs_base / trail.c
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
39 struct trail_debug_t
40 {
41   struct trail_debug_t *prev;
42   const char *table;
43   const char *op;
44 };
45
46 void
47 svn_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
58 static void
59 print_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
81 static svn_error_t *
82 begin_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
114 static svn_error_t *
115 abort_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
143 static svn_error_t *
144 commit_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
193 static svn_error_t *
194 do_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
252 svn_error_t *
253 svn_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
271 svn_error_t *
272 svn_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
283 svn_error_t *
284 svn_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 }