mariadb/storage/bdb/txn/txn_util.c
2005-12-05 10:27:46 -08:00

330 lines
7.1 KiB
C

/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2001-2005
* Sleepycat Software. All rights reserved.
*
* $Id: txn_util.c,v 12.2 2005/09/28 17:45:20 margo Exp $
*/
#include "db_config.h"
#ifndef NO_SYSTEM_INCLUDES
#include <sys/types.h>
#include <string.h>
#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;
int inmem;
} 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));
*/
int
__txn_remevent(dbenv, txn, name, fileid, inmem)
DB_ENV *dbenv;
DB_TXN *txn;
const char *name;
u_int8_t *fileid;
int inmem;
{
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->u.r.inmem = inmem;
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 been 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, DB_NOSYNC)) != 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, e->u.r.inmem)) != 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;
case TXN_CLOSE:
case TXN_TRADE:
case TXN_TRADED:
default:
break;
}
__os_free(dbenv, e);
}
return (ret);
}