提交 458febaf authored 作者: Andrei Tokar's avatar Andrei Tokar

Make TestMultiThreadedKernel to run with MVStore:

fix ABBA deadlock in MVStore between database and meta table lock
fix premature release of database object ids in MVStore
上级 a6d143e4
...@@ -102,6 +102,7 @@ public class Database implements DataHandler { ...@@ -102,6 +102,7 @@ public class Database implements DataHandler {
private static final ThreadLocal<Session> META_LOCK_DEBUGGING; private static final ThreadLocal<Session> META_LOCK_DEBUGGING;
private static final ThreadLocal<Database> META_LOCK_DEBUGGING_DB; private static final ThreadLocal<Database> META_LOCK_DEBUGGING_DB;
private static final ThreadLocal<Throwable> META_LOCK_DEBUGGING_STACK; private static final ThreadLocal<Throwable> META_LOCK_DEBUGGING_STACK;
private static final Session[] EMPTY_SESSION_ARRAY = new Session[0];
static { static {
boolean a = false; boolean a = false;
...@@ -179,7 +180,7 @@ public class Database implements DataHandler { ...@@ -179,7 +180,7 @@ public class Database implements DataHandler {
private int allowLiterals = Constants.ALLOW_LITERALS_ALL; private int allowLiterals = Constants.ALLOW_LITERALS_ALL;
private int powerOffCount = initialPowerOffCount; private int powerOffCount = initialPowerOffCount;
private int closeDelay; private volatile int closeDelay;
private DelayedDatabaseCloser delayedCloser; private DelayedDatabaseCloser delayedCloser;
private volatile boolean closing; private volatile boolean closing;
private boolean ignoreCase; private boolean ignoreCase;
...@@ -786,10 +787,10 @@ public class Database implements DataHandler { ...@@ -786,10 +787,10 @@ public class Database implements DataHandler {
data.create = create; data.create = create;
data.isHidden = true; data.isHidden = true;
data.session = systemSession; data.session = systemSession;
starting = true;
meta = mainSchema.createTable(data); meta = mainSchema.createTable(data);
handleUpgradeIssues(); handleUpgradeIssues();
IndexColumn[] pkCols = IndexColumn.wrap(new Column[] { columnId }); IndexColumn[] pkCols = IndexColumn.wrap(new Column[] { columnId });
starting = true;
metaIdIndex = meta.addIndex(systemSession, "SYS_ID", metaIdIndex = meta.addIndex(systemSession, "SYS_ID",
0, pkCols, IndexType.createPrimaryKey( 0, pkCols, IndexType.createPrimaryKey(
false, false), true, null); false, false), true, null);
...@@ -953,12 +954,15 @@ public class Database implements DataHandler { ...@@ -953,12 +954,15 @@ public class Database implements DataHandler {
} }
} }
private synchronized void addMeta(Session session, DbObject obj) { private void addMeta(Session session, DbObject obj) {
assert Thread.holdsLock(this);
int id = obj.getId(); int id = obj.getId();
if (id > 0 && !starting && !obj.isTemporary()) { if (id > 0 && !starting && !obj.isTemporary()) {
Row r = meta.getTemplateRow(); Row r = meta.getTemplateRow();
MetaRecord.populateRowFromDBObject(obj, r); MetaRecord.populateRowFromDBObject(obj, r);
objectIds.set(id); synchronized (objectIds) {
objectIds.set(id);
}
if (SysProperties.CHECK) { if (SysProperties.CHECK) {
verifyMetaLocked(session); verifyMetaLocked(session);
} }
...@@ -1049,7 +1053,7 @@ public class Database implements DataHandler { ...@@ -1049,7 +1053,7 @@ public class Database implements DataHandler {
* @param session the session * @param session the session
* @param id the id of the object to remove * @param id the id of the object to remove
*/ */
public synchronized void removeMeta(Session session, int id) { public void removeMeta(Session session, int id) {
if (id > 0 && !starting) { if (id > 0 && !starting) {
SearchRow r = meta.getTemplateSimpleRow(false); SearchRow r = meta.getTemplateSimpleRow(false);
r.setValue(0, ValueInt.get(id)); r.setValue(0, ValueInt.get(id));
...@@ -1075,7 +1079,25 @@ public class Database implements DataHandler { ...@@ -1075,7 +1079,25 @@ public class Database implements DataHandler {
unlockMeta(session); unlockMeta(session);
} }
} }
objectIds.clear(id); if (isMVStore()) {
// release of the object id has to be postponed until the end of the transaction,
// otherwise it might be re-used prematurely, and it would make
// rollback impossible or lead to MVMaps name collision,
// so until then ids are accumulated within session
session.releaseDatabaseObjectId(id);
} else {
// but PageStore, on the other hand, for reasons unknown to me,
// requires immediate id release
synchronized (this) {
objectIds.clear(id);
}
}
}
}
void releaseDatabaseObjectIds(BitSet idsToRelease) {
synchronized (objectIds) {
objectIds.andNot(idsToRelease);
} }
} }
...@@ -1307,7 +1329,7 @@ public class Database implements DataHandler { ...@@ -1307,7 +1329,7 @@ public class Database implements DataHandler {
} }
private synchronized void closeAllSessionsException(Session except) { private synchronized void closeAllSessionsException(Session except) {
Session[] all = userSessions.toArray(new Session[userSessions.size()]); Session[] all = userSessions.toArray(EMPTY_SESSION_ARRAY);
for (Session s : all) { for (Session s : all) {
if (s != except) { if (s != except) {
try { try {
...@@ -1573,9 +1595,13 @@ public class Database implements DataHandler { ...@@ -1573,9 +1595,13 @@ public class Database implements DataHandler {
* *
* @return the id * @return the id
*/ */
public synchronized int allocateObjectId() { public int allocateObjectId() {
int i = objectIds.nextClearBit(0); Object lock = isMVStore() ? objectIds : this;
objectIds.set(i); int i;
synchronized (lock) {
i = objectIds.nextClearBit(0);
objectIds.set(i);
}
return i; return i;
} }
...@@ -1763,18 +1789,18 @@ public class Database implements DataHandler { ...@@ -1763,18 +1789,18 @@ public class Database implements DataHandler {
*/ */
public void updateMeta(Session session, DbObject obj) { public void updateMeta(Session session, DbObject obj) {
if (isMVStore()) { if (isMVStore()) {
synchronized (this) { int id = obj.getId();
int id = obj.getId(); if (id > 0) {
if (id > 0) { if (!starting && !obj.isTemporary()) {
if (!starting && !obj.isTemporary()) { Row newRow = meta.getTemplateRow();
Row newRow = meta.getTemplateRow(); MetaRecord.populateRowFromDBObject(obj, newRow);
MetaRecord.populateRowFromDBObject(obj, newRow); Row oldRow = metaIdIndex.getRow(session, id);
Row oldRow = metaIdIndex.getRow(session, id); if (oldRow != null) {
if (oldRow != null) { meta.updateRow(session, oldRow, newRow);
meta.updateRow(session, oldRow, newRow);
}
} }
// for temporary objects }
// for temporary objects
synchronized (objectIds) {
objectIds.set(id); objectIds.set(id);
} }
} }
...@@ -2336,7 +2362,7 @@ public class Database implements DataHandler { ...@@ -2336,7 +2362,7 @@ public class Database implements DataHandler {
return lockMode; return lockMode;
} }
public synchronized void setCloseDelay(int value) { public void setCloseDelay(int value) {
this.closeDelay = value; this.closeDelay = value;
} }
......
...@@ -7,6 +7,7 @@ package org.h2.engine; ...@@ -7,6 +7,7 @@ package org.h2.engine;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
...@@ -162,6 +163,12 @@ public class Session extends SessionWithState implements TransactionStore.Rollba ...@@ -162,6 +163,12 @@ public class Session extends SessionWithState implements TransactionStore.Rollba
private State state = State.INIT; private State state = State.INIT;
private long startStatement = -1; private long startStatement = -1;
/**
* Set of database object ids to be released at the end of transaction
*/
private final BitSet idsToRelease = new BitSet();
public Session(Database database, User user, int id) { public Session(Database database, User user, int id) {
this.database = database; this.database = database;
this.queryTimeout = database.getSettings().maxQueryTimeout; this.queryTimeout = database.getSettings().maxQueryTimeout;
...@@ -627,6 +634,10 @@ public class Session extends SessionWithState implements TransactionStore.Rollba ...@@ -627,6 +634,10 @@ public class Session extends SessionWithState implements TransactionStore.Rollba
return command; return command;
} }
void releaseDatabaseObjectId(int id) {
idsToRelease.set(id);
}
public Database getDatabase() { public Database getDatabase() {
return database; return database;
} }
...@@ -746,6 +757,8 @@ public class Session extends SessionWithState implements TransactionStore.Rollba ...@@ -746,6 +757,8 @@ public class Session extends SessionWithState implements TransactionStore.Rollba
removeLobMap = null; removeLobMap = null;
} }
unlockAll(); unlockAll();
database.releaseDatabaseObjectIds(idsToRelease);
idsToRelease.clear();
} }
/** /**
...@@ -853,6 +866,7 @@ public class Session extends SessionWithState implements TransactionStore.Rollba ...@@ -853,6 +866,7 @@ public class Session extends SessionWithState implements TransactionStore.Rollba
removeTemporaryLobs(false); removeTemporaryLobs(false);
cleanTempTables(true); cleanTempTables(true);
commit(true); // temp table rempval may have opened new transaction
if (undoLog != null) { if (undoLog != null) {
undoLog.clear(); undoLog.clear();
} }
...@@ -961,27 +975,35 @@ public class Session extends SessionWithState implements TransactionStore.Rollba ...@@ -961,27 +975,35 @@ public class Session extends SessionWithState implements TransactionStore.Rollba
private void cleanTempTables(boolean closeSession) { private void cleanTempTables(boolean closeSession) {
if (localTempTables != null && localTempTables.size() > 0) { if (localTempTables != null && localTempTables.size() > 0) {
synchronized (database) { if (database.isMVStore()) {
Iterator<Table> it = localTempTables.values().iterator(); _cleanTempTables(closeSession);
while (it.hasNext()) { } else {
Table table = it.next(); synchronized (database) {
if (closeSession || table.getOnCommitDrop()) { _cleanTempTables(closeSession);
modificationId++; }
table.setModified(); }
it.remove(); }
// Exception thrown in org.h2.engine.Database.removeMeta }
// if line below is missing with TestDeadlock
database.lockMeta(this); private void _cleanTempTables(boolean closeSession) {
table.removeChildrenAndResources(this); Iterator<Table> it = localTempTables.values().iterator();
if (closeSession) { while (it.hasNext()) {
// need to commit, otherwise recovery might Table table = it.next();
// ignore the table removal if (closeSession || table.getOnCommitDrop()) {
database.commit(this); modificationId++;
} table.setModified();
} else if (table.getOnCommitTruncate()) { it.remove();
table.truncate(this); // Exception thrown in org.h2.engine.Database.removeMeta
} // if line below is missing with TestDeadlock
database.lockMeta(this);
table.removeChildrenAndResources(this);
if (closeSession) {
// need to commit, otherwise recovery might
// ignore the table removal
database.commit(this);
} }
} else if (table.getOnCommitTruncate()) {
table.truncate(this);
} }
} }
} }
......
...@@ -809,16 +809,18 @@ public class MVTable extends TableBase { ...@@ -809,16 +809,18 @@ public class MVTable extends TableBase {
} }
database.getStore().removeTable(this); database.getStore().removeTable(this);
super.removeChildrenAndResources(session); super.removeChildrenAndResources(session);
// go backwards because database.removeIndex will // remove scan index (at position 0 on the list) last
// call table.removeIndex
while (indexes.size() > 1) { while (indexes.size() > 1) {
Index index = indexes.get(1); Index index = indexes.get(1);
index.remove(session);
if (index.getName() != null) { if (index.getName() != null) {
database.removeSchemaObject(session, index); database.removeSchemaObject(session, index);
} }
// needed for session temporary indexes // needed for session temporary indexes
indexes.remove(index); indexes.remove(index);
} }
primaryIndex.remove(session);
indexes.clear();
if (SysProperties.CHECK) { if (SysProperties.CHECK) {
for (SchemaObject obj : database for (SchemaObject obj : database
.getAllSchemaObjects(DbObject.INDEX)) { .getAllSchemaObjects(DbObject.INDEX)) {
...@@ -829,8 +831,6 @@ public class MVTable extends TableBase { ...@@ -829,8 +831,6 @@ public class MVTable extends TableBase {
} }
} }
} }
primaryIndex.remove(session);
database.removeMeta(session, getId());
close(session); close(session);
invalidate(); invalidate();
} }
......
...@@ -16,7 +16,6 @@ import org.h2.mvstore.Cursor; ...@@ -16,7 +16,6 @@ import org.h2.mvstore.Cursor;
import org.h2.mvstore.DataUtils; import org.h2.mvstore.DataUtils;
import org.h2.mvstore.MVMap; import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVStore; import org.h2.mvstore.MVStore;
import org.h2.mvstore.Page;
import org.h2.mvstore.WriteBuffer; import org.h2.mvstore.WriteBuffer;
import org.h2.mvstore.type.DataType; import org.h2.mvstore.type.DataType;
import org.h2.mvstore.type.ObjectDataType; import org.h2.mvstore.type.ObjectDataType;
...@@ -416,7 +415,7 @@ public class TransactionStore { ...@@ -416,7 +415,7 @@ public class TransactionStore {
* @param map the map * @param map the map
*/ */
<K, V> void removeMap(TransactionMap<K, V> map) { <K, V> void removeMap(TransactionMap<K, V> map) {
store.removeMap(map.map, true); store.removeMap(map.map, false);
} }
/** /**
......
...@@ -1129,7 +1129,7 @@ public class MetaTable extends Table { ...@@ -1129,7 +1129,7 @@ public class MetaTable extends Table {
add(rows, "info.PAGE_COUNT", add(rows, "info.PAGE_COUNT",
Long.toString(pageCount)); Long.toString(pageCount));
add(rows, "info.PAGE_SIZE", add(rows, "info.PAGE_SIZE",
Integer.toString(pageSize)); Integer.toString(mvStore.getPageSplitSize()));
add(rows, "info.CACHE_MAX_SIZE", add(rows, "info.CACHE_MAX_SIZE",
Integer.toString(mvStore.getCacheSize())); Integer.toString(mvStore.getCacheSize()));
add(rows, "info.CACHE_SIZE", add(rows, "info.CACHE_SIZE",
......
...@@ -42,14 +42,6 @@ public class TestMultiThreadedKernel extends TestDb { ...@@ -42,14 +42,6 @@ public class TestMultiThreadedKernel extends TestDb {
TestBase.createCaller().init().test(); TestBase.createCaller().init().test();
} }
@Override
public boolean isEnabled() {
if (config.mvStore) { // FIXME can't see why test should not work in MVStore mode
return false;
}
return true;
}
@Override @Override
public void test() throws Exception { public void test() throws Exception {
deleteDb("multiThreadedKernel"); deleteDb("multiThreadedKernel");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论