On 2013-06-13 19:58, Andrew Helgeson wrote:
Thanks for your help however I'm still running into this problem with
the latest code. I built a jar from the svn repo at r4849 and it's
still occurring with the same stack trace.
Hi
OK, I can reproduce this now. It only seems to happen about 1 in 5 times
for me, which is why I missed it the first couple of times.
Thomas, this is what seems to be happening:
A Page is being free, but then the code freeing the page is then later
acting as if it still had access to that Page.
The page is freed via the call-path:
at org.h2.store.PageStore.freePage(PageStore.java:1106)
at org.h2.store.PageStore.free(PageStore.java:1227)
at org.h2.store.PageStore.free(PageStore.java:1207)
at org.h2.index.PageBtreeNode.freeRecursive(PageBtreeNode.java:454)
at org.h2.index.PageBtreeIndex.removeAllRows(PageBtreeIndex.java:272)
at org.h2.index.PageBtreeIndex.truncate(PageBtreeIndex.java:262)
at org.h2.table.RegularTable.truncate(RegularTable.java:402)
at org.h2.result.ResultTempTable.dropTable(ResultTempTable.java:155)
at org.h2.result.ResultTempTable.close(ResultTempTable.java:145)
at org.h2.result.LocalResult.close(LocalResult.java:394)
at org.h2.jdbc.JdbcResultSet.closeInternal(JdbcResultSet.java:201)
at org.h2.jdbc.JdbcStatement.closeOldResultSet(JdbcStatement.java:994)
at org.h2.jdbc.JdbcStatement.close(JdbcStatement.java:243)
at
org.h2.jdbc.JdbcPreparedStatement.close(JdbcPreparedStatement.java:1112)
at
org.h2.test.H2CorruptionTest.doDistinctSelect(H2CorruptionTest.java:140)
at org.h2.test.H2CorruptionTest.access$4(H2CorruptionTest.java:123)
at org.h2.test.H2CorruptionTest$1.run(H2CorruptionTest.java:87)
And then the page is re-used via the call-path:
at org.h2.util.CacheLRU.update(CacheLRU.java:134)
at org.h2.store.PageStore.update(PageStore.java:1062)
at org.h2.index.PageDataIndex.removeAllRows(PageDataIndex.java:395)
at org.h2.index.PageDataIndex.truncate(PageDataIndex.java:377)
at org.h2.table.RegularTable.truncate(RegularTable.java:402)
at org.h2.result.ResultTempTable.dropTable(ResultTempTable.java:155)
at org.h2.result.ResultTempTable.close(ResultTempTable.java:145)
at org.h2.result.LocalResult.close(LocalResult.java:394)
at org.h2.jdbc.JdbcResultSet.closeInternal(JdbcResultSet.java:201)
at org.h2.jdbc.JdbcStatement.closeOldResultSet(JdbcStatement.java:994)
at org.h2.jdbc.JdbcStatement.close(JdbcStatement.java:243)
at
org.h2.jdbc.JdbcPreparedStatement.close(JdbcPreparedStatement.java:1112)
at
org.h2.test.H2CorruptionTest.doDistinctSelect(H2CorruptionTest.java:140)
at org.h2.test.H2CorruptionTest.access$4(H2CorruptionTest.java:123)
at org.h2.test.H2CorruptionTest$1.run(H2CorruptionTest.java:87)
Hopefully, this will provide Thomas will enough information to find the
fix.
I think that the problem is that PageDataIndex#removeAllRows() is
calling freeRecursive() on it's root Page, when it should probably only
be freeing the children of the root page, not the root page itself.
But I don't know the code well enough to be sure.
--
You received this message because you are subscribed to the Google Groups "H2
Database" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/h2-database.
For more options, visit https://groups.google.com/groups/opt_out.
Index: .
===================================================================
--- . (revision 4849)
+++ . (working copy)
Property changes on: .
___________________________________________________________________
Modified: svn:ignore
## -36,3 +36,5 ##
error.*
out.txt
java_pid*.hprof
+build
+nbproject
Index: src/main/org/h2/index/PageData.java
===================================================================
--- src/main/org/h2/index/PageData.java (revision 4849)
+++ src/main/org/h2/index/PageData.java (working copy)
@@ -34,7 +34,7 @@
/**
* The index.
*/
- protected final PageDataIndex index;
+ public final PageDataIndex index;
/**
* The page number of the parent.
Index: src/main/org/h2/index/PageDataLeaf.java
===================================================================
--- src/main/org/h2/index/PageDataLeaf.java (revision 4849)
+++ src/main/org/h2/index/PageDataLeaf.java (working copy)
@@ -532,10 +532,7 @@
@Override
public String toString() {
- return "page[" + getPos() + "] data leaf table:" + index.getId() + " "
+ index.getTable().getName() +
- " entries:" + entryCount + " parent:" + parentPageId +
- (firstOverflowPageId == 0 ? "" : " overflow:" +
firstOverflowPageId) +
- " keys:" + Arrays.toString(keys) + " offsets:" +
Arrays.toString(offsets);
+ return "page[" + getPos() + "] data leaf table:" + index.getId() + " "
+ index.getTable().getName();
}
@Override
Index: src/main/org/h2/index/PageDataNode.java
===================================================================
--- src/main/org/h2/index/PageDataNode.java (revision 4849)
+++ src/main/org/h2/index/PageDataNode.java (working copy)
@@ -394,8 +394,7 @@
@Override
public String toString() {
- return "page[" + getPos() + "] data node table:" + index.getId() +
- " entries:" + entryCount + " " + Arrays.toString(childPageIds);
+ return "page[" + getPos() + "] data node table:" + index.getId();
}
@Override
Index: src/main/org/h2/store/PageStore.java
===================================================================
--- src/main/org/h2/store/PageStore.java (revision 4849)
+++ src/main/org/h2/store/PageStore.java (working copy)
@@ -1096,11 +1096,15 @@
return list;
}
+ public final HashMap<Integer, Throwable> whoFreedThisPage = new
HashMap<Integer, Throwable>();
+
private void freePage(int pageId) {
int index = getFreeListId(pageId);
PageFreeList list = getFreeList(index);
firstFreeListIndex = Math.min(index, firstFreeListIndex);
list.free(pageId);
+ whoFreedThisPage.put(pageId, new Throwable());
+ System.out.println("freed page " + pageId);
}
/**
@@ -1166,6 +1170,7 @@
if (trace.isDebugEnabled()) {
// trace.debug("allocatePage " + pos);
}
+ System.out.println("allocated page " + page);
return page;
}
@@ -1903,7 +1908,7 @@
public Cache getCache() {
return cache;
}
-
+
private void checksumSet(byte[] d, int pageId) {
int ps = pageSize;
int type = d[0];
Index: src/main/org/h2/util/CacheLRU.java
===================================================================
--- src/main/org/h2/util/CacheLRU.java (revision 4849)
+++ src/main/org/h2/util/CacheLRU.java (working copy)
@@ -6,12 +6,16 @@
*/
package org.h2.util;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Deque;
import java.util.Map;
import org.h2.constant.SysProperties;
import org.h2.engine.Constants;
+import org.h2.index.PageDataLeaf;
import org.h2.message.DbException;
+import org.h2.store.PageStore;
/**
* A cache implementation based on the last recently used (LRU) algorithm.
@@ -47,7 +51,7 @@
* The current memory used in this cache, in words (4 bytes each).
*/
private int memory;
-
+
CacheLRU(CacheWriter writer, int maxMemoryKb, boolean fifo) {
this.writer = writer;
this.fifo = fifo;
@@ -97,6 +101,7 @@
@Override
public void put(CacheObject rec) {
+ System.out.println("CacheLRU put " + rec);
if (SysProperties.CHECK) {
int pos = rec.getPos();
CacheObject old = find(pos);
@@ -113,14 +118,43 @@
removeOldIfRequired();
}
+ private final Deque<Throwable> updateList = new ArrayDeque<Throwable>(10);
+
@Override
public CacheObject update(int pos, CacheObject rec) {
+ if (pos != rec.getPos()) {
+ throw new IllegalStateException();
+ }
+ if (rec instanceof PageDataLeaf) {
+ PageDataLeaf pdl = (PageDataLeaf) rec;
+ if (pdl.index.getTable().getName().startsWith("TEMP_RESULT_SET_"))
{
+ if (updateList.size()==10) {
+ updateList.removeFirst();
+ }
+ updateList.add(new Throwable("CacheLRU update " + rec));
+ }
+ }
+ System.out.println("CacheLRU update " + rec);
CacheObject old = find(pos);
if (old == null) {
put(rec);
} else {
if (SysProperties.CHECK) {
if (old != rec) {
+
System.out.println("----------------------------------------------------------------------------------------");
+ System.out.println("old!=record pos:" + pos + " old:" +
old + " new:" + rec);
+ for(Throwable ex : updateList) {
+ ex.printStackTrace(System.out);
+ }
+ for (int i=0; i<values.length; i++) {
+ CacheObject v = values[i];
+ while (v != null) {
+ System.out.println(i + " " + v);
+ v = v.cacheChained;
+ }
+ }
+
((PageStore)writer).whoFreedThisPage.get(pos).printStackTrace(System.out);
+
System.out.println("----------------------------------------------------------------------------------------");
DbException.throwInternalError("old!=record pos:" + pos +
" old:" + old + " new:" + rec);
}
}
@@ -250,6 +284,7 @@
int index = pos & mask;
CacheObject rec = values[index];
if (rec == null) {
+ System.out.println("CacheLRU remove false " + pos);
return false;
}
if (rec.getPos() == pos) {
@@ -260,6 +295,7 @@
last = rec;
rec = rec.cacheChained;
if (rec == null) {
+ System.out.println("CacheLRU remove false " + pos);
return false;
}
} while (rec.getPos() != pos);
@@ -275,6 +311,7 @@
DbException.throwInternalError("not removed: " + o);
}
}
+ System.out.println("CacheLRU remove true " + rec);
return true;
}
Index: src/test/org/h2/test/H2CorruptionTest.java
===================================================================
--- src/test/org/h2/test/H2CorruptionTest.java (revision 0)
+++ src/test/org/h2/test/H2CorruptionTest.java (working copy)
@@ -0,0 +1,203 @@
+package org.h2.test;
+
+import java.io.File;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.h2.jdbc.JdbcSQLException;
+
+/**
+ * A repeatable test case of corruption surrounding the
MAX_MEMORY_ROWS_DISTINCT setting of H2
+ *
+ */
+public class H2CorruptionTest {
+
+ private static final String TEST_DB = "d:/temp/h2.test";
+ private static final String TEST_TABLE_NAME = "t_tbl1";
+ private static final String TEST_TABLE_NAME2 = "t_tbl2";
+ private static final Integer MEMORY_DISTINCT_ROWS = 500;
+
+ private static final int MAX_CONNECTION_ATTEMPTS = 3;
+
+ private static int ageCounter = 0;
+
+ private static AtomicBoolean finished = new AtomicBoolean(false);
+
+ public static void doWork() throws Exception {
+ File dbFile = new File(TEST_DB + ".h2.db");
+ if (dbFile.delete()) {
+ System.out.println("Deleted old database");
+ }
+
+ Connection conn = openConnection();
+ conn.setAutoCommit(false);
+
+ addTable(conn);
+
+ startSelectThread();
+
+ for (int i = 1; i <= MEMORY_DISTINCT_ROWS * 4; ++i) {
+ if (i % 10 == 0) {
+ System.out.println("Inserted " + i + " rows");
+ }
+
+ beginTrans(conn);
+ try {
+ // Insert a new row
+ doInsert(conn);
+ commitTrans(conn);
+
+ } catch (Exception e) {
+ executeSQL("ROLLBACK;", conn);
+
+ throw new RuntimeException(e);
+ }
+ }
+
+ finished.set(true);
+ }
+
+ private static void beginTrans(Connection conn) throws Exception {
+ executeSQL("BEGIN;", conn);
+ }
+
+ private static void commitTrans(Connection conn) throws Exception {
+ executeSQL("COMMIT;", conn);
+ }
+
+ private static void rollbackTrans(Connection conn) throws Exception {
+ executeSQL("ROLLBACK;", conn);
+ }
+
+ private static Thread startSelectThread() {
+ Thread t = new Thread() {
+
+ @Override
+ public void run() {
+ System.out.println("\n\n---- Starting select
thread ----");
+ try {
+ Connection conn = openConnection();
+
+ while (!finished.get()) {
+ beginTrans(conn);
+ try {
+ doDistinctSelect(conn);
+ commitTrans(conn);
+ } finally {
+ rollbackTrans(conn);
+ }
+ } // End while
+ } catch (Throwable t) {
+ System.err.println("Error in select
thread: " + t);
+ throw new RuntimeException(t);
+ } finally {
+ System.out.println("---- Select thread
exiting ----");
+ }
+ }
+ };
+
+ t.setDaemon(true);
+ t.start();
+ return t;
+ }
+
+ private static void doInsert(Connection conn) throws Exception {
+ StringBuilder sqlBuilder = new StringBuilder();
+ sqlBuilder.append("INSERT INTO ").append(TEST_TABLE_NAME);
+ sqlBuilder.append(" (age) values (");
+ sqlBuilder.append(ageCounter++ % (MEMORY_DISTINCT_ROWS *
2)).append(")");
+
+ executeSQL(sqlBuilder.toString(), conn);
+
+ sqlBuilder = new StringBuilder();
+ sqlBuilder.append("INSERT INTO ").append(TEST_TABLE_NAME2);
+ sqlBuilder.append(" (age_fk) values (");
+ sqlBuilder.append(ageCounter % (MEMORY_DISTINCT_ROWS *
2)).append(")");
+
+ executeSQL(sqlBuilder.toString(), conn);
+ }
+
+ private static int doDistinctSelect(Connection conn) throws Exception {
+ String sql = "SELECT distinct(age) FROM " + TEST_TABLE_NAME //
+ + " INNER JOIN " + TEST_TABLE_NAME2 //
+ + " ON age=age_fk";
+
+ PreparedStatement ps = conn.prepareStatement(sql);
+ ResultSet rs = ps.executeQuery();
+
+ try {
+ rs.getArray(1);
+ } catch (JdbcSQLException e) {
+ // Intentionally empty;
+ }
+
+ rs.last();
+ int numRows = rs.getRow();
+
+ ps.close();
+
+ return numRows;
+ }
+
+ private static void addTable(Connection conn) throws Exception {
+ StringBuilder sqlBuilder = new StringBuilder();
+ sqlBuilder.append("CREATE TABLE ").append(TEST_TABLE_NAME);
+ sqlBuilder.append(" (id INT PRIMARY KEY AUTO_INCREMENT NOT
NULL,");
+ sqlBuilder.append("age INTEGER NOT NULL)");
+
+ executeSQL(sqlBuilder.toString(), conn);
+
+ sqlBuilder = new StringBuilder();
+ sqlBuilder.append("CREATE TABLE ").append(TEST_TABLE_NAME2);
+ sqlBuilder.append(" (id2 INT PRIMARY KEY AUTO_INCREMENT NOT
NULL,");
+ sqlBuilder.append("age_fk INTEGER NOT NULL)");
+
+ executeSQL(sqlBuilder.toString(), conn);
+ }
+
+ private static Connection openConnection() throws Exception {
+ Connection conn = null;
+
+ String url = "jdbc:h2:"
+ + TEST_DB //
+ +
";DEFAULT_LOCK_TIMEOUT=30000;WRITE_DELAY=0;ACCESS_MODE_DATA=rwd;DB_CLOSE_ON_EXIT=FALSE;MAX_MEMORY_ROWS_DISTINCT="
//
+ + MEMORY_DISTINCT_ROWS;
+
+ int retries = 0;
+ Exception failedConnectionException = null;
+ while (conn == null && retries < MAX_CONNECTION_ATTEMPTS) {
+ try {
+ System.out.println("Connecting to database: "
+ TEST_DB);
+ System.out.println("Connection url: " + url);
+ conn = DriverManager.getConnection(url,
"test", "pass");
+ failedConnectionException = null;
+ } catch (Exception e) {
+ failedConnectionException = e;
+ System.err.println(" connection denied.
cause: " + e.getMessage());
+ } finally {
+ retries++;
+ }
+ }
+
+ // bail out if necessary
+ if (failedConnectionException != null) {
+ throw failedConnectionException;
+ }
+
+ return conn;
+ }
+
+ private static void executeSQL(String sql, Connection conn) throws
Exception {
+ PreparedStatement ps = conn.prepareStatement(sql);
+ ps.execute();
+ ps.close();
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.setProperty("h2.check2", "true");
+ doWork();
+ }
+}