Imported Upstream version 5.3.21
[platform/upstream/libdb.git] / src / txn / txn_rec.c
1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 1996, 2012 Oracle and/or its affiliates.  All rights reserved.
5  */
6 /*
7  * Copyright (c) 1996
8  *      The President and Fellows of Harvard University.  All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  * $Id$
35  */
36
37 #include "db_config.h"
38
39 #include "db_int.h"
40 #include "dbinc/db_page.h"
41 #include "dbinc/lock.h"
42 #include "dbinc/txn.h"
43 #include "dbinc/db_am.h"
44
45 /*
46  * PUBLIC: int __txn_regop_recover
47  * PUBLIC:    __P((ENV *, DBT *, DB_LSN *, db_recops, void *));
48  *
49  * These records are only ever written for commits.  Normally, we redo any
50  * committed transaction, however if we are doing recovery to a timestamp, then
51  * we may treat transactions that committed after the timestamp as aborted.
52  */
53 int
54 __txn_regop_recover(env, dbtp, lsnp, op, info)
55         ENV *env;
56         DBT *dbtp;
57         DB_LSN *lsnp;
58         db_recops op;
59         void *info;
60 {
61         __txn_regop_args *argp;
62         DB_TXNHEAD *headp;
63         int ret;
64         u_int32_t status;
65
66 #ifdef DEBUG_RECOVER
67         (void)__txn_regop_print(env, dbtp, lsnp, op, info);
68 #endif
69
70         if ((ret = __txn_regop_read(env, dbtp->data, &argp)) != 0)
71                 return (ret);
72
73         headp = info;
74         /*
75          * We are only ever called during FORWARD_ROLL or BACKWARD_ROLL.
76          * We check for the former explicitly and the last two clauses
77          * apply to the BACKWARD_ROLL case.
78          */
79
80         if (op == DB_TXN_FORWARD_ROLL) {
81                 /*
82                  * If this was a 2-phase-commit transaction, then it
83                  * might already have been removed from the list, and
84                  * that's OK.  Ignore the return code from remove.
85                  */
86                 if ((ret = __db_txnlist_remove(env,
87                     info, argp->txnp->txnid)) != DB_NOTFOUND && ret != 0)
88                         goto err;
89         } else if ((env->dbenv->tx_timestamp != 0 &&
90             argp->timestamp > (int32_t)env->dbenv->tx_timestamp) ||
91             (!IS_ZERO_LSN(headp->trunc_lsn) &&
92             LOG_COMPARE(&headp->trunc_lsn, lsnp) < 0)) {
93                 /*
94                  * We failed either the timestamp check or the trunc_lsn check,
95                  * so we treat this as an abort even if it was a commit record.
96                  */
97                 if ((ret = __db_txnlist_update(env, info,
98                     argp->txnp->txnid, TXN_ABORT, NULL, &status, 1)) != 0)
99                         goto err;
100                 else if (status != TXN_IGNORE && status != TXN_OK)
101                         goto err;
102         } else {
103                 /* This is a normal commit; mark it appropriately. */
104                 if ((ret = __db_txnlist_update(env,
105                     info, argp->txnp->txnid, argp->opcode, lsnp,
106                     &status, 0)) == DB_NOTFOUND) {
107                         if ((ret = __db_txnlist_add(env,
108                             info, argp->txnp->txnid,
109                             argp->opcode == TXN_ABORT ?
110                             TXN_IGNORE : argp->opcode, lsnp)) != 0)
111                                 goto err;
112                 } else if (ret != 0 ||
113                     (status != TXN_IGNORE && status != TXN_OK))
114                         goto err;
115         }
116
117         if (ret == 0)
118                 *lsnp = argp->prev_lsn;
119
120         if (0) {
121 err:            __db_errx(env, DB_STR_A("4514",
122                     "txnid %lx commit record found, already on commit list",
123                     "%lx"), (u_long)argp->txnp->txnid);
124                 ret = EINVAL;
125         }
126         __os_free(env, argp);
127
128         return (ret);
129 }
130
131 /*
132  * PUBLIC: int __txn_prepare_recover
133  * PUBLIC:    __P((ENV *, DBT *, DB_LSN *, db_recops, void *));
134  *
135  * These records are only ever written for prepares.
136  */
137 int
138 __txn_prepare_recover(env, dbtp, lsnp, op, info)
139         ENV *env;
140         DBT *dbtp;
141         DB_LSN *lsnp;
142         db_recops op;
143         void *info;
144 {
145         __txn_prepare_args *argp;
146         DBT *lock_dbt;
147         DB_TXNHEAD *headp;
148         DB_LOCKTAB *lt;
149         u_int32_t status;
150         int ret;
151
152 #ifdef DEBUG_RECOVER
153         (void)__txn_prepare_print(env, dbtp, lsnp, op, info);
154 #endif
155
156         if ((ret = __txn_prepare_read(env, dbtp->data, &argp)) != 0)
157                 return (ret);
158
159         if (argp->opcode != TXN_PREPARE && argp->opcode != TXN_ABORT) {
160                 ret = EINVAL;
161                 goto err;
162         }
163         headp = info;
164
165         /*
166          * The return value here is either a DB_NOTFOUND or it is
167          * the transaction status from the list.  It is not a normal
168          * error return, so we must make sure that in each of the
169          * cases below, we overwrite the ret value so we return
170          * appropriately.
171          */
172         ret = __db_txnlist_find(env, info, argp->txnp->txnid, &status);
173
174         /*
175          * If we are rolling forward, then an aborted prepare
176          * indicates that this may be the last record we'll see for
177          * this transaction ID, so we should remove it from the list.
178          */
179
180         if (op == DB_TXN_FORWARD_ROLL) {
181                 if ((ret = __db_txnlist_remove(env,
182                     info, argp->txnp->txnid)) != 0)
183                         goto txn_err;
184         } else if (op == DB_TXN_BACKWARD_ROLL && status == TXN_PREPARE) {
185                 /*
186                  * On the backward pass, we have four possibilities:
187                  * 1. The transaction is already committed, no-op.
188                  * 2. The transaction is already aborted, no-op.
189                  * 3. The prepare failed and was aborted, mark as abort.
190                  * 4. The transaction is neither committed nor aborted.
191                  *       Treat this like a commit and roll forward so that
192                  *       the transaction can be resurrected in the region.
193                  * We handle cases 3 and 4 here; cases 1 and 2
194                  * are the final clause below.
195                  */
196                 if (argp->opcode == TXN_ABORT) {
197                         if ((ret = __db_txnlist_update(env,
198                              info, argp->txnp->txnid,
199                              TXN_ABORT, NULL, &status, 0)) != 0 &&
200                              status != TXN_PREPARE)
201                                 goto txn_err;
202                         ret = 0;
203                 }
204                 /*
205                  * This is prepared, but not yet committed transaction.  We
206                  * need to add it to the transaction list, so that it gets
207                  * rolled forward. We also have to add it to the region's
208                  * internal state so it can be properly aborted or committed
209                  * after recovery (see txn_recover).
210                  */
211                 else if ((ret = __db_txnlist_remove(env,
212                     info, argp->txnp->txnid)) != 0) {
213 txn_err:                __db_errx(env,
214                             DB_STR_A("4515",
215                             "transaction not in list %lx", "%lx"),
216                             (u_long)argp->txnp->txnid);
217                         ret = DB_NOTFOUND;
218                 } else if (IS_ZERO_LSN(headp->trunc_lsn) ||
219                     LOG_COMPARE(&headp->trunc_lsn, lsnp) >= 0) {
220                         if ((ret = __db_txnlist_add(env,
221                            info, argp->txnp->txnid, TXN_COMMIT, lsnp)) == 0) {
222                                 /* Re-acquire the locks for this transaction. */
223                                 lock_dbt = &argp->locks;
224                                 if (LOCKING_ON(env)) {
225                                         lt = env->lk_handle;
226                                         if ((ret = __lock_getlocker(lt,
227                                                 argp->txnp->txnid, 1,
228                                                 &argp->txnp->locker)) != 0)
229                                                 goto err;
230                                         if ((ret = __lock_get_list(env,
231                                             argp->txnp->locker, 0,
232                                             DB_LOCK_WRITE, lock_dbt)) != 0)
233                                                 goto err;
234                                 }
235
236                                 ret = __txn_restore_txn(env, lsnp, argp);
237                         }
238                 }
239         } else
240                 ret = 0;
241
242         if (ret == 0)
243                 *lsnp = argp->prev_lsn;
244
245 err:    __os_free(env, argp);
246
247         return (ret);
248 }
249
250 /*
251  * PUBLIC: int __txn_ckp_recover
252  * PUBLIC: __P((ENV *, DBT *, DB_LSN *, db_recops, void *));
253  */
254 int
255 __txn_ckp_recover(env, dbtp, lsnp, op, info)
256         ENV *env;
257         DBT *dbtp;
258         DB_LSN *lsnp;
259         db_recops op;
260         void *info;
261 {
262         __txn_ckp_args *argp;
263         int ret;
264
265 #ifdef DEBUG_RECOVER
266         __txn_ckp_print(env, dbtp, lsnp, op, info);
267 #endif
268         if ((ret = __txn_ckp_read(env, dbtp->data, &argp)) != 0)
269                 return (ret);
270
271         if (op == DB_TXN_BACKWARD_ROLL)
272                 __db_txnlist_ckp(env, info, lsnp);
273
274         *lsnp = argp->last_ckp;
275         __os_free(env, argp);
276         return (DB_TXN_CKP);
277 }
278
279 /*
280  * __txn_child_recover
281  *      Recover a commit record for a child transaction.
282  *
283  * PUBLIC: int __txn_child_recover
284  * PUBLIC:    __P((ENV *, DBT *, DB_LSN *, db_recops, void *));
285  */
286 int
287 __txn_child_recover(env, dbtp, lsnp, op, info)
288         ENV *env;
289         DBT *dbtp;
290         DB_LSN *lsnp;
291         db_recops op;
292         void *info;
293 {
294         __txn_child_args *argp;
295         u_int32_t c_stat, p_stat, tmpstat;
296         int ret, t_ret;
297
298 #ifdef DEBUG_RECOVER
299         (void)__txn_child_print(env, dbtp, lsnp, op, info);
300 #endif
301         if ((ret = __txn_child_read(env, dbtp->data, &argp)) != 0)
302                 return (ret);
303
304         /*
305          * This is a record in a PARENT's log trail indicating that a
306          * child committed.  If we are aborting, return the childs last
307          * record's LSN.  If we are in recovery, then if the
308          * parent is committing, we set ourselves up to commit, else
309          * we do nothing.
310          */
311         if (op == DB_TXN_ABORT) {
312                 *lsnp = argp->c_lsn;
313                 ret = __db_txnlist_lsnadd(env, info, &argp->prev_lsn);
314                 goto out;
315         } else if (op == DB_TXN_BACKWARD_ROLL) {
316                 /* Child might exist -- look for it. */
317                 ret = __db_txnlist_find(env, info, argp->child, &c_stat);
318                 t_ret =
319                     __db_txnlist_find(env, info, argp->txnp->txnid, &p_stat);
320                 if (ret != 0 && ret != DB_NOTFOUND)
321                         goto out;
322                 if (t_ret != 0 && t_ret != DB_NOTFOUND) {
323                         ret = t_ret;
324                         goto out;
325                 }
326                 /*
327                  * If the parent is in state COMMIT or IGNORE, then we apply
328                  * that to the child, else we need to abort the child.
329                  */
330
331                 if (ret == DB_NOTFOUND  ||
332                     c_stat == TXN_OK || c_stat == TXN_COMMIT) {
333                         if (t_ret == DB_NOTFOUND ||
334                              (p_stat != TXN_COMMIT  && p_stat != TXN_IGNORE))
335                                 c_stat = TXN_ABORT;
336                         else
337                                 c_stat = p_stat;
338
339                         if (ret == DB_NOTFOUND)
340                                 ret = __db_txnlist_add(env,
341                                      info, argp->child, c_stat, NULL);
342                         else
343                                 ret = __db_txnlist_update(env, info,
344                                      argp->child, c_stat, NULL, &tmpstat, 0);
345                 } else if (c_stat == TXN_EXPECTED) {
346                         /*
347                          * The open after this create succeeded.  If the
348                          * parent succeeded, we don't want to redo; if the
349                          * parent aborted, we do want to undo.
350                          */
351                         switch (p_stat) {
352                         case TXN_COMMIT:
353                         case TXN_IGNORE:
354                                 c_stat = TXN_IGNORE;
355                                 break;
356                         default:
357                                 c_stat = TXN_ABORT;
358                         }
359                         ret = __db_txnlist_update(env,
360                             info, argp->child, c_stat, NULL, &tmpstat, 0);
361                 } else if (c_stat == TXN_UNEXPECTED) {
362                         /*
363                          * The open after this create failed.  If the parent
364                          * is rolling forward, we need to roll forward.  If
365                          * the parent failed, then we do not want to abort
366                          * (because the file may not be the one in which we
367                          * are interested).
368                          */
369                         ret = __db_txnlist_update(env, info, argp->child,
370                             p_stat == TXN_COMMIT ? TXN_COMMIT : TXN_IGNORE,
371                             NULL, &tmpstat, 0);
372                 }
373         } else if (op == DB_TXN_OPENFILES) {
374                 /*
375                  * If we have a partial subtransaction, then the whole
376                  * transaction should be ignored.
377                  */
378                 if ((ret = __db_txnlist_find(env,
379                     info, argp->child, &c_stat)) == DB_NOTFOUND)
380                         ret = __db_txnlist_update(env, info,
381                              argp->txnp->txnid, TXN_IGNORE,
382                              NULL, &p_stat, 1);
383         } else if (DB_REDO(op)) {
384                 /* Forward Roll */
385                 if ((ret =
386                     __db_txnlist_remove(env, info, argp->child)) != 0)
387                         __db_errx(env, DB_STR_A("4516",
388                             "Transaction not in list %x", "%x"), argp->child);
389         }
390
391         if (ret == 0)
392                 *lsnp = argp->prev_lsn;
393
394 out:    __os_free(env, argp);
395
396         return (ret);
397 }
398
399 /*
400  * __txn_restore_txn --
401  *      Using only during XA recovery.  If we find any transactions that are
402  * prepared, but not yet committed, then we need to restore the transaction's
403  * state into the shared region, because the TM is going to issue an abort
404  * or commit and we need to respond correctly.
405  *
406  * lsnp is the LSN of the returned LSN
407  * argp is the prepare record (in an appropriate structure)
408  *
409  * PUBLIC: int __txn_restore_txn __P((ENV *, DB_LSN *, __txn_prepare_args *));
410  */
411 int
412 __txn_restore_txn(env, lsnp, argp)
413         ENV *env;
414         DB_LSN *lsnp;
415         __txn_prepare_args *argp;
416 {
417         DB_TXNMGR *mgr;
418         DB_TXNREGION *region;
419         TXN_DETAIL *td;
420         int ret;
421
422         if (argp->gid.size == 0)
423                 return (0);
424
425         mgr = env->tx_handle;
426         region = mgr->reginfo.primary;
427         TXN_SYSTEM_LOCK(env);
428
429         /* Allocate a new transaction detail structure. */
430         if ((ret = __env_alloc(&mgr->reginfo, sizeof(TXN_DETAIL), &td)) != 0) {
431                 TXN_SYSTEM_UNLOCK(env);
432                 return (ret);
433         }
434
435         /* Place transaction on active transaction list. */
436         SH_TAILQ_INSERT_HEAD(&region->active_txn, td, links, __txn_detail);
437         region->curtxns++;
438
439         td->txnid = argp->txnp->txnid;
440         __os_id(env->dbenv, &td->pid, &td->tid);
441         td->last_lsn = *lsnp;
442         td->begin_lsn = argp->begin_lsn;
443         td->parent = INVALID_ROFF;
444         td->name = INVALID_ROFF;
445         SH_TAILQ_INIT(&td->kids);
446         MAX_LSN(td->read_lsn);
447         MAX_LSN(td->visible_lsn);
448         td->mvcc_ref = 0;
449         td->mvcc_mtx = MUTEX_INVALID;
450         td->status = TXN_PREPARED;
451         td->flags = TXN_DTL_RESTORED;
452         memcpy(td->gid, argp->gid.data, argp->gid.size);
453         td->nlog_dbs = 0;
454         td->nlog_slots = TXN_NSLOTS;
455         td->log_dbs = R_OFFSET(&mgr->reginfo, td->slots);
456
457         region->stat.st_nrestores++;
458 #ifdef HAVE_STATISTICS
459         STAT_INC(env, txn, nactive, region->stat.st_nactive, td->txnid);
460         if (region->stat.st_nactive > region->stat.st_maxnactive)
461                 STAT_SET(env, txn, maxnactive, region->stat.st_maxnactive,
462                     region->stat.st_nactive, td->txnid);
463 #endif
464         TXN_SYSTEM_UNLOCK(env);
465         return (0);
466 }
467
468 /*
469  * __txn_recycle_recover --
470  *      Recovery function for recycle.
471  *
472  * PUBLIC: int __txn_recycle_recover
473  * PUBLIC:   __P((ENV *, DBT *, DB_LSN *, db_recops, void *));
474  */
475 int
476 __txn_recycle_recover(env, dbtp, lsnp, op, info)
477         ENV *env;
478         DBT *dbtp;
479         DB_LSN *lsnp;
480         db_recops op;
481         void *info;
482 {
483         __txn_recycle_args *argp;
484         int ret;
485
486 #ifdef DEBUG_RECOVER
487         (void)__txn_child_print(env, dbtp, lsnp, op, info);
488 #endif
489         if ((ret = __txn_recycle_read(env, dbtp->data, &argp)) != 0)
490                 return (ret);
491
492         COMPQUIET(lsnp, NULL);
493
494         if ((ret = __db_txnlist_gen(env, info,
495             DB_UNDO(op) ? -1 : 1, argp->min, argp->max)) != 0)
496                 return (ret);
497
498         __os_free(env, argp);
499
500         return (0);
501 }
502
503 /*
504  * PUBLIC: int __txn_regop_42_recover
505  * PUBLIC:    __P((ENV *, DBT *, DB_LSN *, db_recops, void *));
506  *
507  * These records are only ever written for commits.  Normally, we redo any
508  * committed transaction, however if we are doing recovery to a timestamp, then
509  * we may treat transactions that committed after the timestamp as aborted.
510  */
511 int
512 __txn_regop_42_recover(env, dbtp, lsnp, op, info)
513         ENV *env;
514         DBT *dbtp;
515         DB_LSN *lsnp;
516         db_recops op;
517         void *info;
518 {
519         __txn_regop_42_args *argp;
520         DB_TXNHEAD *headp;
521         u_int32_t status;
522         int ret;
523
524 #ifdef DEBUG_RECOVER
525         (void)__txn_regop_42_print(env, dbtp, lsnp, op, info);
526 #endif
527
528         if ((ret = __txn_regop_42_read(env, dbtp->data, &argp)) != 0)
529                 return (ret);
530
531         headp = info;
532         /*
533          * We are only ever called during FORWARD_ROLL or BACKWARD_ROLL.
534          * We check for the former explicitly and the last two clauses
535          * apply to the BACKWARD_ROLL case.
536          */
537
538         if (op == DB_TXN_FORWARD_ROLL) {
539                 /*
540                  * If this was a 2-phase-commit transaction, then it
541                  * might already have been removed from the list, and
542                  * that's OK.  Ignore the return code from remove.
543                  */
544                 if ((ret = __db_txnlist_remove(env,
545                     info, argp->txnp->txnid)) != DB_NOTFOUND && ret != 0)
546                         goto err;
547         } else if ((env->dbenv->tx_timestamp != 0 &&
548             argp->timestamp > (int32_t)env->dbenv->tx_timestamp) ||
549             (!IS_ZERO_LSN(headp->trunc_lsn) &&
550             LOG_COMPARE(&headp->trunc_lsn, lsnp) < 0)) {
551                 /*
552                  * We failed either the timestamp check or the trunc_lsn check,
553                  * so we treat this as an abort even if it was a commit record.
554                  */
555                 if ((ret = __db_txnlist_update(env, info,
556                     argp->txnp->txnid, TXN_ABORT, NULL, &status, 1)) != 0)
557                         goto err;
558                 else if (status != TXN_IGNORE && status != TXN_OK)
559                         goto err;
560         } else {
561                 /* This is a normal commit; mark it appropriately. */
562                 if ((ret = __db_txnlist_update(env,
563                     info, argp->txnp->txnid, argp->opcode, lsnp,
564                     &status, 0)) == DB_NOTFOUND) {
565                         if ((ret = __db_txnlist_add(env,
566                             info, argp->txnp->txnid,
567                             argp->opcode == TXN_ABORT ?
568                             TXN_IGNORE : argp->opcode, lsnp)) != 0)
569                                 goto err;
570                 } else if (ret != 0 ||
571                     (status != TXN_IGNORE && status != TXN_OK))
572                         goto err;
573         }
574
575         if (ret == 0)
576                 *lsnp = argp->prev_lsn;
577
578         if (0) {
579 err:            __db_errx(env, DB_STR_A("4517",
580                     "txnid %lx commit record found, already on commit list",
581                     "%lx"), (u_long)argp->txnp->txnid);
582                 ret = EINVAL;
583         }
584         __os_free(env, argp);
585
586         return (ret);
587 }
588
589 /*
590  * PUBLIC: int __txn_ckp_42_recover
591  * PUBLIC: __P((ENV *, DBT *, DB_LSN *, db_recops, void *));
592  */
593 int
594 __txn_ckp_42_recover(env, dbtp, lsnp, op, info)
595         ENV *env;
596         DBT *dbtp;
597         DB_LSN *lsnp;
598         db_recops op;
599         void *info;
600 {
601         __txn_ckp_42_args *argp;
602         int ret;
603
604 #ifdef DEBUG_RECOVER
605         __txn_ckp_42_print(env, dbtp, lsnp, op, info);
606 #endif
607         if ((ret = __txn_ckp_42_read(env, dbtp->data, &argp)) != 0)
608                 return (ret);
609
610         if (op == DB_TXN_BACKWARD_ROLL)
611                 __db_txnlist_ckp(env, info, lsnp);
612
613         *lsnp = argp->last_ckp;
614         __os_free(env, argp);
615         return (DB_TXN_CKP);
616 }