/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2001-2003 * Sleepycat Software. All rights reserved. */ #include "db_config.h" #ifndef lint static const char revid[] = "$Id: txn_util.c,v 1.2 2004/03/30 01:24:47 jtownsen Exp $"; #endif /* not lint */ #ifndef NO_SYSTEM_INCLUDES #include #include #endif #include "db_int.h" #include "dbinc/db_page.h" #include "dbinc/db_shash.h" #include "dbinc/lock.h" #include "dbinc/mp.h" #include "dbinc/txn.h" #include "dbinc/db_am.h" typedef struct __txn_event TXN_EVENT; struct __txn_event { TXN_EVENT_T op; TAILQ_ENTRY(__txn_event) links; union { struct { /* Delayed close. */ DB *dbp; } c; struct { /* Delayed remove. */ char *name; u_int8_t *fileid; } r; struct { /* Lock event. */ DB_LOCK lock; u_int32_t locker; DB *dbp; } t; } u; }; /* * __txn_closeevent -- * * Creates a close event that can be added to the [so-called] commit list, so * that we can redo a failed DB handle close once we've aborted the transaction. * * PUBLIC: int __txn_closeevent __P((DB_ENV *, DB_TXN *, DB *)); */ int __txn_closeevent(dbenv, txn, dbp) DB_ENV *dbenv; DB_TXN *txn; DB *dbp; { int ret; TXN_EVENT *e; e = NULL; if ((ret = __os_calloc(dbenv, 1, sizeof(TXN_EVENT), &e)) != 0) return (ret); e->u.c.dbp = dbp; e->op = TXN_CLOSE; TAILQ_INSERT_TAIL(&txn->events, e, links); return (0); } /* * __txn_remevent -- * * Creates a remove event that can be added to the commit list. * * PUBLIC: int __txn_remevent __P((DB_ENV *, * PUBLIC: DB_TXN *, const char *, u_int8_t*)); */ int __txn_remevent(dbenv, txn, name, fileid) DB_ENV *dbenv; DB_TXN *txn; const char *name; u_int8_t *fileid; { int ret; TXN_EVENT *e; e = NULL; if ((ret = __os_calloc(dbenv, 1, sizeof(TXN_EVENT), &e)) != 0) return (ret); if ((ret = __os_strdup(dbenv, name, &e->u.r.name)) != 0) goto err; if (fileid != NULL) { if ((ret = __os_calloc(dbenv, 1, DB_FILE_ID_LEN, &e->u.r.fileid)) != 0) return (ret); memcpy(e->u.r.fileid, fileid, DB_FILE_ID_LEN); } e->op = TXN_REMOVE; TAILQ_INSERT_TAIL(&txn->events, e, links); return (0); err: if (e != NULL) __os_free(dbenv, e); return (ret); } /* * __txn_remrem -- * Remove a remove event because the remove has be superceeded, * by a create of the same name, for example. * * PUBLIC: void __txn_remrem __P((DB_ENV *, DB_TXN *, const char *)); */ void __txn_remrem(dbenv, txn, name) DB_ENV *dbenv; DB_TXN *txn; const char *name; { TXN_EVENT *e, *next_e; for (e = TAILQ_FIRST(&txn->events); e != NULL; e = next_e) { next_e = TAILQ_NEXT(e, links); if (e->op != TXN_REMOVE || strcmp(name, e->u.r.name) != 0) continue; TAILQ_REMOVE(&txn->events, e, links); __os_free(dbenv, e->u.r.name); if (e->u.r.fileid != NULL) __os_free(dbenv, e->u.r.fileid); __os_free(dbenv, e); } return; } /* * __txn_lockevent -- * * Add a lockevent to the commit-queue. The lock event indicates a locker * trade. * * PUBLIC: int __txn_lockevent __P((DB_ENV *, * PUBLIC: DB_TXN *, DB *, DB_LOCK *, u_int32_t)); */ int __txn_lockevent(dbenv, txn, dbp, lock, locker) DB_ENV *dbenv; DB_TXN *txn; DB *dbp; DB_LOCK *lock; u_int32_t locker; { int ret; TXN_EVENT *e; if (!LOCKING_ON(dbenv)) return (0); e = NULL; if ((ret = __os_calloc(dbenv, 1, sizeof(TXN_EVENT), &e)) != 0) return (ret); e->u.t.locker = locker; e->u.t.lock = *lock; e->u.t.dbp = dbp; e->op = TXN_TRADE; TAILQ_INSERT_TAIL(&txn->events, e, links); return (0); } /* * __txn_remlock -- * Remove a lock event because the locker is going away. We can remove * by lock (using offset) or by locker_id (or by both). * * PUBLIC: void __txn_remlock __P((DB_ENV *, DB_TXN *, DB_LOCK *, u_int32_t)); */ void __txn_remlock(dbenv, txn, lock, locker) DB_ENV *dbenv; DB_TXN *txn; DB_LOCK *lock; u_int32_t locker; { TXN_EVENT *e, *next_e; for (e = TAILQ_FIRST(&txn->events); e != NULL; e = next_e) { next_e = TAILQ_NEXT(e, links); if ((e->op != TXN_TRADE && e->op != TXN_TRADED) || (e->u.t.lock.off != lock->off && e->u.t.locker != locker)) continue; TAILQ_REMOVE(&txn->events, e, links); __os_free(dbenv, e); } return; } /* * __txn_doevents -- * Process the list of events associated with a transaction. On commit, * apply the events; on abort, just toss the entries. * * PUBLIC: int __txn_doevents __P((DB_ENV *, DB_TXN *, int, int)); */ #define DO_TRADE do { \ memset(&req, 0, sizeof(req)); \ req.lock = e->u.t.lock; \ req.op = DB_LOCK_TRADE; \ t_ret = __lock_vec(dbenv, e->u.t.locker, 0, &req, 1, NULL); \ if (t_ret == 0) \ e->u.t.dbp->cur_lid = e->u.t.locker; \ else if (t_ret == DB_NOTFOUND) \ t_ret = 0; \ if (t_ret != 0 && ret == 0) \ ret = t_ret; \ e->op = TXN_TRADED; \ } while (0) int __txn_doevents(dbenv, txn, opcode, preprocess) DB_ENV *dbenv; DB_TXN *txn; int opcode, preprocess; { DB_LOCKREQ req; TXN_EVENT *e; int ret, t_ret; ret = 0; /* * This phase only gets called if we have a phase where we * release read locks. Since not all paths will call this * phase, we have to check for it below as well. So, when * we do the trade, we update the opcode of the entry so that * we don't try the trade again. */ if (preprocess) { for (e = TAILQ_FIRST(&txn->events); e != NULL; e = TAILQ_NEXT(e, links)) { if (e->op != TXN_TRADE) continue; DO_TRADE; } return (ret); } /* * Prepare should only cause a preprocess, since the transaction * isn't over. */ DB_ASSERT(opcode != TXN_PREPARE); while ((e = TAILQ_FIRST(&txn->events)) != NULL) { TAILQ_REMOVE(&txn->events, e, links); /* * Most deferred events should only happen on * commits, not aborts or prepares. The one exception * is a close which gets done on commit and abort, but * not prepare. If we're not doing operations, then we * can just go free resources. */ if (opcode == TXN_ABORT && e->op != TXN_CLOSE) goto dofree; switch (e->op) { case TXN_CLOSE: /* If we didn't abort this txn, we screwed up badly. */ DB_ASSERT(opcode == TXN_ABORT); if ((t_ret = __db_close(e->u.c.dbp, NULL, 0)) != 0 && ret == 0) ret = t_ret; break; case TXN_REMOVE: if (e->u.r.fileid != NULL) { if ((t_ret = __memp_nameop(dbenv, e->u.r.fileid, NULL, e->u.r.name, NULL)) != 0 && ret == 0) ret = t_ret; } else if ((t_ret = __os_unlink(dbenv, e->u.r.name)) != 0 && ret == 0) ret = t_ret; break; case TXN_TRADE: DO_TRADE; /* Fall through */ case TXN_TRADED: /* Downgrade the lock. */ if ((t_ret = __lock_downgrade(dbenv, &e->u.t.lock, DB_LOCK_READ, 0)) != 0 && ret == 0) ret = t_ret; break; default: /* This had better never happen. */ DB_ASSERT(0); } dofree: /* Free resources here. */ switch (e->op) { case TXN_REMOVE: if (e->u.r.fileid != NULL) __os_free(dbenv, e->u.r.fileid); __os_free(dbenv, e->u.r.name); break; default: break; } __os_free(dbenv, e); } return (ret); }