📄 db_dispatch.c
字号:
{ DB_TXNHEAD *hp; COMPQUIET(dbenv, NULL); hp = (DB_TXNHEAD *)listp; if (IS_ZERO_LSN(hp->ckplsn) && !IS_ZERO_LSN(hp->maxlsn) && log_compare(&hp->maxlsn, ckp_lsn) >= 0) hp->ckplsn = *ckp_lsn;}/* * __db_txnlist_end -- * Discard transaction linked list. Print out any error messages * for deleted files. * * PUBLIC: void __db_txnlist_end __P((DB_ENV *, void *)); */void__db_txnlist_end(dbenv, listp) DB_ENV *dbenv; void *listp;{ DB_TXNHEAD *hp; DB_TXNLIST *p; int i; if ((hp = (DB_TXNHEAD *)listp) == NULL) return; for (i = 0; i < hp->nslots; i++) while (hp != NULL && (p = LIST_FIRST(&hp->head[i])) != NULL) { LIST_REMOVE(p, links); switch (p->type) { case TXNLIST_LSN: __os_free(dbenv, p->u.l.lsn_array); break; default: /* * Possibly an incomplete DB_TXNLIST; just * free it. */ break; } __os_free(dbenv, p); } if (hp->gen_array != NULL) __os_free(dbenv, hp->gen_array); __os_free(dbenv, listp);}/* * __db_txnlist_find -- * Checks to see if a txnid with the current generation is in the * txnid list. This returns TXN_NOTFOUND if the item isn't in the * list otherwise it returns (like __db_txnlist_find_internal) * the status of the transaction. A txnid of 0 means the record * was generated while not in a transaction. * * PUBLIC: int __db_txnlist_find __P((DB_ENV *, void *, u_int32_t)); */int__db_txnlist_find(dbenv, listp, txnid) DB_ENV *dbenv; void *listp; u_int32_t txnid;{ DB_TXNLIST *entry; if (txnid == 0) return (TXN_NOTFOUND); return (__db_txnlist_find_internal(dbenv, listp, TXNLIST_TXNID, txnid, NULL, &entry, 0));}/* * __db_txnlist_update -- * Change the status of an existing transaction entry. * Returns TXN_NOTFOUND if no such entry exists. * * PUBLIC: int __db_txnlist_update __P((DB_ENV *, * PUBLIC: void *, u_int32_t, u_int32_t, DB_LSN *)); */int__db_txnlist_update(dbenv, listp, txnid, status, lsn) DB_ENV *dbenv; void *listp; u_int32_t txnid; u_int32_t status; DB_LSN *lsn;{ DB_TXNHEAD *hp; DB_TXNLIST *elp; int ret; if (txnid == 0) return (TXN_NOTFOUND); hp = (DB_TXNHEAD *)listp; ret = __db_txnlist_find_internal(dbenv, listp, TXNLIST_TXNID, txnid, NULL, &elp, 0); if (ret == TXN_NOTFOUND) return (ret); elp->u.t.status = status; if (lsn != NULL && IS_ZERO_LSN(hp->maxlsn) && status == TXN_COMMIT) hp->maxlsn = *lsn; return (ret);}/* * __db_txnlist_find_internal -- * Find an entry on the transaction list. If the entry is not there or * the list pointer is not initialized we return TXN_NOTFOUND. If the * item is found, we return the status. Currently we always call this * with an initialized list pointer but checking for NULL keeps it general. */static int__db_txnlist_find_internal(dbenv, listp, type, txnid, uid, txnlistp, delete) DB_ENV *dbenv; void *listp; db_txnlist_type type; u_int32_t txnid; u_int8_t uid[DB_FILE_ID_LEN]; DB_TXNLIST **txnlistp; int delete;{ DB_TXNHEAD *hp; DB_TXNLIST *p; int32_t generation; u_int32_t hash; struct __db_headlink *head; int i, ret; if ((hp = (DB_TXNHEAD *)listp) == NULL) return (TXN_NOTFOUND); switch (type) { case TXNLIST_TXNID: hash = txnid; /* Find the most recent generation containing this ID */ for (i = 0; i <= hp->generation; i++) /* The range may wrap around the end. */ if (hp->gen_array[i].txn_min < hp->gen_array[i].txn_max ? (txnid >= hp->gen_array[i].txn_min && txnid <= hp->gen_array[i].txn_max) : (txnid >= hp->gen_array[i].txn_min || txnid <= hp->gen_array[i].txn_max)) break; DB_ASSERT(i <= hp->generation); generation = hp->gen_array[i].generation; break; case TXNLIST_PGNO: memcpy(&hash, uid, sizeof(hash)); generation = 0; break; default: DB_ASSERT(0); return (EINVAL); } head = &hp->head[DB_TXNLIST_MASK(hp, hash)]; for (p = LIST_FIRST(head); p != NULL; p = LIST_NEXT(p, links)) { if (p->type != type) continue; switch (type) { case TXNLIST_TXNID: if (p->u.t.txnid != txnid || generation != p->u.t.generation) continue; ret = p->u.t.status; break; case TXNLIST_PGNO: if (memcmp(uid, p->u.p.uid, DB_FILE_ID_LEN) != 0) continue; ret = 0; break; default: DB_ASSERT(0); ret = EINVAL; } if (delete == 1) { LIST_REMOVE(p, links); __os_free(dbenv, p); } else if (p != LIST_FIRST(head)) { /* Move it to head of list. */ LIST_REMOVE(p, links); LIST_INSERT_HEAD(head, p, links); } *txnlistp = p; return (ret); } return (TXN_NOTFOUND);}/* * __db_txnlist_gen -- * Change the current generation number. * * PUBLIC: int __db_txnlist_gen __P((DB_ENV *, * PUBLIC: void *, int, u_int32_t, u_int32_t)); */int__db_txnlist_gen(dbenv, listp, incr, min, max) DB_ENV *dbenv; void *listp; int incr; u_int32_t min, max;{ DB_TXNHEAD *hp; int ret; /* * During recovery generation numbers keep track of "restart" * checkpoints and recycle records. Restart checkpoints occur * whenever we take a checkpoint and there are no outstanding * transactions. When that happens, we can reset transaction IDs * back to TXNID_MINIMUM. Currently we only do the reset * at then end of recovery. Recycle records occrur when txnids * are exhausted during runtime. A free range of ids is identified * and logged. This code maintains a stack of ranges. A txnid * is given the generation number of the first range it falls into * in the stack. */ hp = (DB_TXNHEAD *)listp; hp->generation += incr; if (incr < 0) memmove(hp->gen_array, &hp->gen_array[1], (hp->generation + 1) * sizeof(hp->gen_array[0])); else { if (hp->generation >= hp->gen_alloc) { hp->gen_alloc *= 2; if ((ret = __os_realloc(dbenv, hp->gen_alloc * sizeof(hp->gen_array[0]), &hp->gen_array)) != 0) return (ret); } memmove(&hp->gen_array[1], &hp->gen_array[0], hp->generation * sizeof(hp->gen_array[0])); hp->gen_array[0].generation = hp->generation; hp->gen_array[0].txn_min = min; hp->gen_array[0].txn_max = max; } return (0);}#define TXN_BUBBLE(AP, MAX) { \ int __j; \ DB_LSN __tmp; \ \ for (__j = 0; __j < MAX - 1; __j++) \ if (log_compare(&AP[__j], &AP[__j + 1]) < 0) { \ __tmp = AP[__j]; \ AP[__j] = AP[__j + 1]; \ AP[__j + 1] = __tmp; \ } \}/* * __db_txnlist_lsnadd -- * Add to or re-sort the transaction list lsn entry. Note that since this * is used during an abort, the __txn_undo code calls into the "recovery" * subsystem explicitly, and there is only a single TXNLIST_LSN entry on * the list. * * PUBLIC: int __db_txnlist_lsnadd __P((DB_ENV *, void *, DB_LSN *, u_int32_t)); */int__db_txnlist_lsnadd(dbenv, listp, lsnp, flags) DB_ENV *dbenv; void *listp; DB_LSN *lsnp; u_int32_t flags;{ DB_TXNHEAD *hp; DB_TXNLIST *elp; int i, ret; hp = (DB_TXNHEAD *)listp; for (elp = LIST_FIRST(&hp->head[0]); elp != NULL; elp = LIST_NEXT(elp, links)) if (elp->type == TXNLIST_LSN) break; if (elp == NULL) return (DB_SURPRISE_KID); if (LF_ISSET(TXNLIST_NEW)) { if (elp->u.l.ntxns >= elp->u.l.maxn) { if ((ret = __os_realloc(dbenv, 2 * elp->u.l.maxn * sizeof(DB_LSN), &elp->u.l.lsn_array)) != 0) return (ret); elp->u.l.maxn *= 2; } elp->u.l.lsn_array[elp->u.l.ntxns++] = *lsnp; } else /* Simply replace the 0th element. */ elp->u.l.lsn_array[0] = *lsnp; /* * If we just added a new entry and there may be NULL entries, so we * have to do a complete bubble sort, not just trickle a changed entry * around. */ for (i = 0; i < (!LF_ISSET(TXNLIST_NEW) ? 1 : elp->u.l.ntxns); i++) TXN_BUBBLE(elp->u.l.lsn_array, elp->u.l.ntxns); *lsnp = elp->u.l.lsn_array[0]; return (0);}/* * __db_txnlist_lsninit -- * Initialize a transaction list with an lsn array entry. * * PUBLIC: int __db_txnlist_lsninit __P((DB_ENV *, DB_TXNHEAD *, DB_LSN *)); */int__db_txnlist_lsninit(dbenv, hp, lsnp) DB_ENV *dbenv; DB_TXNHEAD *hp; DB_LSN *lsnp;{ DB_TXNLIST *elp; int ret; elp = NULL; if ((ret = __os_malloc(dbenv, sizeof(DB_TXNLIST), &elp)) != 0) goto err; LIST_INSERT_HEAD(&hp->head[0], elp, links); if ((ret = __os_malloc(dbenv, 12 * sizeof(DB_LSN), &elp->u.l.lsn_array)) != 0) goto err; elp->type = TXNLIST_LSN; elp->u.l.maxn = 12; elp->u.l.ntxns = 1; elp->u.l.lsn_array[0] = *lsnp; return (0);err: __db_txnlist_end(dbenv, hp); return (ret);}/* * __db_add_limbo -- add pages to the limbo list. * Get the file information and call pgnoadd for each page. * * PUBLIC: int __db_add_limbo __P((DB_ENV *, * PUBLIC: void *, int32_t, db_pgno_t, int32_t)); */int__db_add_limbo(dbenv, info, fileid, pgno, count) DB_ENV *dbenv; void *info; int32_t fileid; db_pgno_t pgno; int32_t count;{ DB_LOG *dblp; FNAME *fnp; int ret; dblp = dbenv->lg_handle; if ((ret = __dbreg_id_to_fname(dblp, fileid, 0, &fnp)) != 0) return (ret); do { if ((ret = __db_txnlist_pgnoadd(dbenv, info, fileid, fnp->ufid, R_ADDR(&dblp->reginfo, fnp->name_off), pgno)) != 0) return (ret); pgno++; } while (--count != 0); return (0);}/* * __db_do_the_limbo -- move pages from limbo to free. * * Limbo processing is what ensures that we correctly handle and * recover from page allocations. During recovery, for each database, * we process each in-question allocation, link them into the free list * and then write out the new meta-data page that contains the pointer * to the new beginning of the free list. On an abort, we use our * standard __db_free mechanism in a compensating transaction which logs * the specific modifications to the free list. * * If we run out of log space during an abort, then we can't write the * compensating transaction, so we abandon the idea of a compenating * transaction, and go back to processing how we do during recovery. * The reason that this is not the norm is that it's expensive: it requires * that we flush any database with an in-question allocation. Thus if * a compensating transaction fails, we never try to restart it. * * Since files may be open and closed within transactions (in particular, * the master database for subdatabases), we must be prepared to open * files during this process. If there is a compensating transaction, we * can open the files in that transaction. If this was an abort and there * is no compensating transaction, then we've got to perform these opens * in the context of the aborting transaction so that we do not deadlock. * During recovery, there's no locking, so this isn't an issue. * * What you want to keep in mind when reading this is that there are two * algorithms going on here: ctxn == NULL, then we're either in recovery * or our compensating transaction has failed and we're doing the * "create list and write meta-data page" algorithm. Otherwise, we're in * an abort and doing the "use compensating transaction" algorithm. * * PUBLIC: int __db_do_the_limbo __P((DB_ENV *, * PUBLIC: DB_TXN *, DB_TXN *, DB_TXNHEAD *)); */int__db_do_the_limbo(dbenv, ptxn, txn, hp) DB_ENV *dbenv; DB_TXN *ptxn, *txn; DB_TXNHEAD *hp;{ DB_TXNLIST *elp; int h, ret; ret = 0; /* * The slots correspond to hash buckets. We've hashed the * fileids into hash buckets and need to pick up all affected * files. (There will only be a single slot for an abort.) */ for (h = 0; h < hp->nslots; h++) { if ((elp = LIST_FIRST(&hp->head[h])) == NULL) continue; if (ptxn != NULL) { if ((ret = __db_limbo_move(dbenv, ptxn, txn, elp)) != 0) goto err; } else if ((ret = __db_limbo_bucket(dbenv, txn, elp)) != 0) goto err; }err: if (ret != 0) { __db_err(dbenv, "Fatal error in abort of an allocation"); ret = __db_panic(dbenv, ret); } return (ret);}/* Limbo support routines. *//* * __db_lock_move -- * Move a lock from child to parent. */static int__db_lock_move(dbenv, fileid, pgno, mode, ptxn, txn) DB_ENV *dbenv; u_int8_t *fileid;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -