|
|
|
@ -995,7 +995,8 @@ typedef struct MDB_dbx { |
|
|
|
|
*/ |
|
|
|
|
struct MDB_txn { |
|
|
|
|
MDB_txn *mt_parent; /**< parent of a nested txn */ |
|
|
|
|
MDB_txn *mt_child; /**< nested txn under this txn */ |
|
|
|
|
/** Nested txn under this txn, set together with flag #MDB_TXN_HAS_CHILD */ |
|
|
|
|
MDB_txn *mt_child; |
|
|
|
|
pgno_t mt_next_pgno; /**< next unallocated page */ |
|
|
|
|
/** The ID of this transaction. IDs are integers incrementing from 1.
|
|
|
|
|
* Only committed write transactions increment the ID. If a transaction |
|
|
|
@ -1043,8 +1044,9 @@ struct MDB_txn { |
|
|
|
|
MDB_cursor **mt_cursors; |
|
|
|
|
/** Array of flags for each DB */ |
|
|
|
|
unsigned char *mt_dbflags; |
|
|
|
|
/** Number of DB records in use. This number only ever increments;
|
|
|
|
|
* we don't decrement it when individual DB handles are closed. |
|
|
|
|
/** Number of DB records in use, or 0 when the txn is finished.
|
|
|
|
|
* This number only ever increments until the txn finishes; we |
|
|
|
|
* don't decrement it when individual DB handles are closed. |
|
|
|
|
*/ |
|
|
|
|
MDB_dbi mt_numdbs; |
|
|
|
|
|
|
|
|
@ -1057,9 +1059,13 @@ struct MDB_txn { |
|
|
|
|
#define MDB_TXN_RDONLY MDB_RDONLY /**< read-only transaction */ |
|
|
|
|
/* internal txn flags */ |
|
|
|
|
#define MDB_TXN_WRITEMAP MDB_WRITEMAP /**< copy of #MDB_env flag in writers */ |
|
|
|
|
#define MDB_TXN_FINISHED 0x01 /**< txn is finished or never began */ |
|
|
|
|
#define MDB_TXN_ERROR 0x02 /**< txn is unusable after an error */ |
|
|
|
|
#define MDB_TXN_DIRTY 0x04 /**< must write, even if dirty list is empty */ |
|
|
|
|
#define MDB_TXN_SPILLS 0x08 /**< txn or a parent has spilled pages */ |
|
|
|
|
#define MDB_TXN_HAS_CHILD 0x10 /**< txn has an #MDB_txn.%mt_child */ |
|
|
|
|
/** most operations on the txn are currently illegal */ |
|
|
|
|
#define MDB_TXN_BLOCKED (MDB_TXN_FINISHED|MDB_TXN_ERROR|MDB_TXN_HAS_CHILD) |
|
|
|
|
/** @} */ |
|
|
|
|
unsigned int mt_flags; /**< @ref mdb_txn */ |
|
|
|
|
/** #dirty_list room: Array size - \#dirty pages visible to this txn.
|
|
|
|
@ -1356,7 +1362,7 @@ static char *const mdb_errstr[] = { |
|
|
|
|
"MDB_MAP_RESIZED: Database contents grew beyond environment mapsize", |
|
|
|
|
"MDB_INCOMPATIBLE: Operation and DB incompatible, or DB flags changed", |
|
|
|
|
"MDB_BAD_RSLOT: Invalid reuse of reader locktable slot", |
|
|
|
|
"MDB_BAD_TXN: Transaction cannot recover - it must be aborted", |
|
|
|
|
"MDB_BAD_TXN: Transaction must abort, has a child, or is invalid", |
|
|
|
|
"MDB_BAD_VALSIZE: Unsupported size of key/DB name/data, or wrong DUPFIXED size", |
|
|
|
|
"MDB_BAD_DBI: The specified DBI handle was closed/changed unexpectedly", |
|
|
|
|
}; |
|
|
|
@ -2725,9 +2731,7 @@ mdb_txn_begin(MDB_env *env, MDB_txn *parent, unsigned int flags, MDB_txn **ret) |
|
|
|
|
if (parent) { |
|
|
|
|
/* Nested transactions: Max 1 child, write txns only, no writemap */ |
|
|
|
|
flags |= parent->mt_flags; |
|
|
|
|
if (parent->mt_child || |
|
|
|
|
(flags & (MDB_RDONLY|MDB_WRITEMAP|MDB_TXN_ERROR))) |
|
|
|
|
{ |
|
|
|
|
if (flags & (MDB_RDONLY|MDB_WRITEMAP|MDB_TXN_BLOCKED)) { |
|
|
|
|
return (parent->mt_flags & MDB_TXN_RDONLY) ? EINVAL : MDB_BAD_TXN; |
|
|
|
|
} |
|
|
|
|
/* Child txns save MDB_pgstate and use own copy of cursors */ |
|
|
|
@ -2769,6 +2773,7 @@ mdb_txn_begin(MDB_env *env, MDB_txn *parent, unsigned int flags, MDB_txn **ret) |
|
|
|
|
txn->mt_u.dirty_list[0].mid = 0; |
|
|
|
|
txn->mt_spill_pgs = NULL; |
|
|
|
|
txn->mt_next_pgno = parent->mt_next_pgno; |
|
|
|
|
parent->mt_flags |= MDB_TXN_HAS_CHILD; |
|
|
|
|
parent->mt_child = txn; |
|
|
|
|
txn->mt_parent = parent; |
|
|
|
|
txn->mt_numdbs = parent->mt_numdbs; |
|
|
|
@ -2885,9 +2890,10 @@ mdb_txn_end(MDB_txn *txn, unsigned mode) |
|
|
|
|
txn->mt_u.reader = NULL; |
|
|
|
|
} /* else txn owns the slot until it does MDB_END_SLOT */ |
|
|
|
|
} |
|
|
|
|
txn->mt_numdbs = 0; /* close nothing if called again */ |
|
|
|
|
txn->mt_numdbs = 0; /* prevent further DBI activity */ |
|
|
|
|
txn->mt_flags |= MDB_TXN_FINISHED; |
|
|
|
|
txn->mt_dbxs = NULL; /* mark txn as reset */ |
|
|
|
|
} else { |
|
|
|
|
} else if (!F_ISSET(txn->mt_flags, MDB_TXN_FINISHED)) { |
|
|
|
|
pgno_t *pghead = env->me_pghead; |
|
|
|
|
|
|
|
|
|
if (!(mode & MDB_END_UPDATE)) /* !(already closed cursors) */ |
|
|
|
@ -2896,6 +2902,9 @@ mdb_txn_end(MDB_txn *txn, unsigned mode) |
|
|
|
|
mdb_dlist_free(txn); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
txn->mt_numdbs = 0; |
|
|
|
|
txn->mt_flags = MDB_TXN_FINISHED; |
|
|
|
|
|
|
|
|
|
if (!txn->mt_parent) { |
|
|
|
|
mdb_midl_shrink(&txn->mt_free_pgs); |
|
|
|
|
env->me_free_pgs = txn->mt_free_pgs; |
|
|
|
@ -2911,6 +2920,7 @@ mdb_txn_end(MDB_txn *txn, unsigned mode) |
|
|
|
|
UNLOCK_MUTEX(env->me_wmutex); |
|
|
|
|
} else { |
|
|
|
|
txn->mt_parent->mt_child = NULL; |
|
|
|
|
txn->mt_parent->mt_flags &= ~MDB_TXN_HAS_CHILD; |
|
|
|
|
env->me_pgstate = ((MDB_ntxn *)txn)->mnt_pgstate; |
|
|
|
|
mdb_midl_free(txn->mt_free_pgs); |
|
|
|
|
mdb_midl_free(txn->mt_spill_pgs); |
|
|
|
@ -3318,8 +3328,8 @@ mdb_txn_commit(MDB_txn *txn) |
|
|
|
|
goto done; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (F_ISSET(txn->mt_flags, MDB_TXN_ERROR)) { |
|
|
|
|
DPUTS("error flag is set, can't commit"); |
|
|
|
|
if (txn->mt_flags & (MDB_TXN_FINISHED|MDB_TXN_ERROR)) { |
|
|
|
|
DPUTS("txn has failed/finished, can't commit"); |
|
|
|
|
if (txn->mt_parent) |
|
|
|
|
txn->mt_parent->mt_flags |= MDB_TXN_ERROR; |
|
|
|
|
rc = MDB_BAD_TXN; |
|
|
|
@ -4742,6 +4752,7 @@ mdb_env_open(MDB_env *env, const char *path, unsigned int flags, mdb_mode_t mode |
|
|
|
|
txn->mt_dbflags = (unsigned char *)(txn->mt_dbiseqs + env->me_maxdbs); |
|
|
|
|
txn->mt_env = env; |
|
|
|
|
txn->mt_dbxs = env->me_dbxs; |
|
|
|
|
txn->mt_flags = MDB_TXN_FINISHED; |
|
|
|
|
env->me_txn0 = txn; |
|
|
|
|
} else { |
|
|
|
|
rc = ENOMEM; |
|
|
|
@ -5284,8 +5295,8 @@ mdb_page_search(MDB_cursor *mc, MDB_val *key, int flags) |
|
|
|
|
/* Make sure the txn is still viable, then find the root from
|
|
|
|
|
* the txn's db table and set it as the root of the cursor's stack. |
|
|
|
|
*/ |
|
|
|
|
if (F_ISSET(mc->mc_txn->mt_flags, MDB_TXN_ERROR)) { |
|
|
|
|
DPUTS("transaction has failed, must abort"); |
|
|
|
|
if (mc->mc_txn->mt_flags & MDB_TXN_BLOCKED) { |
|
|
|
|
DPUTS("transaction may not be used now"); |
|
|
|
|
return MDB_BAD_TXN; |
|
|
|
|
} else { |
|
|
|
|
/* Make sure we're using an up-to-date root */ |
|
|
|
@ -5472,7 +5483,7 @@ mdb_get(MDB_txn *txn, MDB_dbi dbi, |
|
|
|
|
if (!key || !data || !TXN_DBI_EXIST(txn, dbi, DB_USRVALID)) |
|
|
|
|
return EINVAL; |
|
|
|
|
|
|
|
|
|
if (txn->mt_flags & MDB_TXN_ERROR) |
|
|
|
|
if (txn->mt_flags & MDB_TXN_BLOCKED) |
|
|
|
|
return MDB_BAD_TXN; |
|
|
|
|
|
|
|
|
|
mdb_cursor_init(&mc, txn, dbi, &mx); |
|
|
|
@ -5993,7 +6004,7 @@ mdb_cursor_get(MDB_cursor *mc, MDB_val *key, MDB_val *data, |
|
|
|
|
if (mc == NULL) |
|
|
|
|
return EINVAL; |
|
|
|
|
|
|
|
|
|
if (mc->mc_txn->mt_flags & MDB_TXN_ERROR) |
|
|
|
|
if (mc->mc_txn->mt_flags & MDB_TXN_BLOCKED) |
|
|
|
|
return MDB_BAD_TXN; |
|
|
|
|
|
|
|
|
|
switch (op) { |
|
|
|
@ -6223,7 +6234,7 @@ mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data, |
|
|
|
|
nospill = flags & MDB_NOSPILL; |
|
|
|
|
flags &= ~MDB_NOSPILL; |
|
|
|
|
|
|
|
|
|
if (mc->mc_txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_ERROR)) |
|
|
|
|
if (mc->mc_txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED)) |
|
|
|
|
return (mc->mc_txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN; |
|
|
|
|
|
|
|
|
|
if (key->mv_size-1 >= ENV_MAXKEY(env)) |
|
|
|
@ -6716,7 +6727,7 @@ mdb_cursor_del(MDB_cursor *mc, unsigned int flags) |
|
|
|
|
MDB_page *mp; |
|
|
|
|
int rc; |
|
|
|
|
|
|
|
|
|
if (mc->mc_txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_ERROR)) |
|
|
|
|
if (mc->mc_txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED)) |
|
|
|
|
return (mc->mc_txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN; |
|
|
|
|
|
|
|
|
|
if (!(mc->mc_flags & C_INITIALIZED)) |
|
|
|
@ -7285,7 +7296,7 @@ mdb_cursor_open(MDB_txn *txn, MDB_dbi dbi, MDB_cursor **ret) |
|
|
|
|
if (!ret || !TXN_DBI_EXIST(txn, dbi, DB_VALID)) |
|
|
|
|
return EINVAL; |
|
|
|
|
|
|
|
|
|
if (txn->mt_flags & MDB_TXN_ERROR) |
|
|
|
|
if (txn->mt_flags & MDB_TXN_BLOCKED) |
|
|
|
|
return MDB_BAD_TXN; |
|
|
|
|
|
|
|
|
|
/* Allow read access to the freelist */ |
|
|
|
@ -7320,7 +7331,7 @@ mdb_cursor_renew(MDB_txn *txn, MDB_cursor *mc) |
|
|
|
|
if ((mc->mc_flags & C_UNTRACK) || txn->mt_cursors) |
|
|
|
|
return EINVAL; |
|
|
|
|
|
|
|
|
|
if (txn->mt_flags & MDB_TXN_ERROR) |
|
|
|
|
if (txn->mt_flags & MDB_TXN_BLOCKED) |
|
|
|
|
return MDB_BAD_TXN; |
|
|
|
|
|
|
|
|
|
mdb_cursor_init(mc, txn, mc->mc_dbi, mc->mc_xcursor); |
|
|
|
@ -7339,7 +7350,7 @@ mdb_cursor_count(MDB_cursor *mc, size_t *countp) |
|
|
|
|
if (mc->mc_xcursor == NULL) |
|
|
|
|
return MDB_INCOMPATIBLE; |
|
|
|
|
|
|
|
|
|
if (mc->mc_txn->mt_flags & MDB_TXN_ERROR) |
|
|
|
|
if (mc->mc_txn->mt_flags & MDB_TXN_BLOCKED) |
|
|
|
|
return MDB_BAD_TXN; |
|
|
|
|
|
|
|
|
|
if (!(mc->mc_flags & C_INITIALIZED)) |
|
|
|
@ -8089,7 +8100,7 @@ mdb_del(MDB_txn *txn, MDB_dbi dbi, |
|
|
|
|
if (!key || !TXN_DBI_EXIST(txn, dbi, DB_USRVALID)) |
|
|
|
|
return EINVAL; |
|
|
|
|
|
|
|
|
|
if (txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_ERROR)) |
|
|
|
|
if (txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED)) |
|
|
|
|
return (txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN; |
|
|
|
|
|
|
|
|
|
if (!F_ISSET(txn->mt_dbs[dbi].md_flags, MDB_DUPSORT)) { |
|
|
|
@ -9276,7 +9287,7 @@ int mdb_dbi_open(MDB_txn *txn, const char *name, unsigned int flags, MDB_dbi *db |
|
|
|
|
|
|
|
|
|
if (flags & ~VALID_FLAGS) |
|
|
|
|
return EINVAL; |
|
|
|
|
if (txn->mt_flags & MDB_TXN_ERROR) |
|
|
|
|
if (txn->mt_flags & MDB_TXN_BLOCKED) |
|
|
|
|
return MDB_BAD_TXN; |
|
|
|
|
|
|
|
|
|
/* main DB? */ |
|
|
|
@ -9374,7 +9385,7 @@ mdb_stat(MDB_txn *txn, MDB_dbi dbi, MDB_stat *arg) |
|
|
|
|
if (!arg || !TXN_DBI_EXIST(txn, dbi, DB_VALID)) |
|
|
|
|
return EINVAL; |
|
|
|
|
|
|
|
|
|
if (txn->mt_flags & MDB_TXN_ERROR) |
|
|
|
|
if (txn->mt_flags & MDB_TXN_BLOCKED) |
|
|
|
|
return MDB_BAD_TXN; |
|
|
|
|
|
|
|
|
|
if (txn->mt_dbflags[dbi] & DB_STALE) { |
|
|
|
|