提交 99d6c3ee authored 作者: Thomas Mueller's avatar Thomas Mueller

Formatting

上级 1f55186b
...@@ -36,8 +36,10 @@ public class Engine implements SessionFactory { ...@@ -36,8 +36,10 @@ public class Engine implements SessionFactory {
private Engine() { private Engine() {
// use getInstance() // use getInstance()
if (SysProperties.THREAD_DEADLOCK_DETECTOR) {
ThreadDeadlockDetector.init(); ThreadDeadlockDetector.init();
} }
}
public static Engine getInstance() { public static Engine getInstance() {
return INSTANCE; return INSTANCE;
......
...@@ -453,6 +453,14 @@ public class SysProperties { ...@@ -453,6 +453,14 @@ public class SysProperties {
public static final boolean TRACE_IO = public static final boolean TRACE_IO =
Utils.getProperty("h2.traceIO", false); Utils.getProperty("h2.traceIO", false);
/**
* System property <code>h2.threadDeadlockDetector</code>
* (default: false).<br />
* Detect thread deadlocks in a background thread.
*/
public static final boolean THREAD_DEADLOCK_DETECTOR =
Utils.getProperty("h2.threadDeadlockDetector", false);
/** /**
* System property <code>h2.implicitRelativePath</code> * System property <code>h2.implicitRelativePath</code>
* (default: true for version 1.3, false for version 1.4).<br /> * (default: true for version 1.3, false for version 1.4).<br />
......
...@@ -2314,7 +2314,7 @@ public class MVStore { ...@@ -2314,7 +2314,7 @@ public class MVStore {
releaseWriteBuffer(buff); releaseWriteBuffer(buff);
// only really needed if we remove many chunks, when writes are // only really needed if we remove many chunks, when writes are
// re-ordered - but we do it always, because rollback is not // re-ordered - but we do it always, because rollback is not
// performance criticial // performance critical
sync(); sync();
} }
lastChunk = keep; lastChunk = keep;
......
...@@ -49,9 +49,32 @@ import org.h2.value.Value; ...@@ -49,9 +49,32 @@ import org.h2.value.Value;
*/ */
public class MVTable extends TableBase { public class MVTable extends TableBase {
public static final DebuggingThreadLocal<String> WAITING_FOR_LOCK = new DebuggingThreadLocal<String>(); /**
public static final DebuggingThreadLocal<ArrayList<String>> EXCLUSIVE_LOCKS = new DebuggingThreadLocal<ArrayList<String>>(); * The table name this thread is waiting to lock.
public static final DebuggingThreadLocal<ArrayList<String>> SHARED_LOCKS = new DebuggingThreadLocal<ArrayList<String>>(); */
public static final DebuggingThreadLocal<String> WAITING_FOR_LOCK;
/**
* The table names this thread has exclusively locked.
*/
public static final DebuggingThreadLocal<ArrayList<String>> EXCLUSIVE_LOCKS;
/**
* The tables names this thread has a shared lock on.
*/
public static final DebuggingThreadLocal<ArrayList<String>> SHARED_LOCKS;
static {
if (SysProperties.THREAD_DEADLOCK_DETECTOR) {
WAITING_FOR_LOCK = new DebuggingThreadLocal<String>();
EXCLUSIVE_LOCKS = new DebuggingThreadLocal<ArrayList<String>>();
SHARED_LOCKS = new DebuggingThreadLocal<ArrayList<String>>();
} else {
WAITING_FOR_LOCK = null;
EXCLUSIVE_LOCKS = null;
SHARED_LOCKS = null;
}
}
private MVPrimaryIndex primaryIndex; private MVPrimaryIndex primaryIndex;
private final ArrayList<Index> indexes = New.arrayList(); private final ArrayList<Index> indexes = New.arrayList();
...@@ -136,13 +159,17 @@ public class MVTable extends TableBase { ...@@ -136,13 +159,17 @@ public class MVTable extends TableBase {
return true; return true;
} }
session.setWaitForLock(this, Thread.currentThread()); session.setWaitForLock(this, Thread.currentThread());
if (SysProperties.THREAD_DEADLOCK_DETECTOR) {
WAITING_FOR_LOCK.set(getName()); WAITING_FOR_LOCK.set(getName());
}
waitingSessions.addLast(session); waitingSessions.addLast(session);
try { try {
doLock1(session, lockMode, exclusive); doLock1(session, lockMode, exclusive);
} finally { } finally {
session.setWaitForLock(null, null); session.setWaitForLock(null, null);
if (SysProperties.THREAD_DEADLOCK_DETECTOR) {
WAITING_FOR_LOCK.remove(); WAITING_FOR_LOCK.remove();
}
waitingSessions.remove(session); waitingSessions.remove(session);
} }
} }
...@@ -226,19 +253,23 @@ public class MVTable extends TableBase { ...@@ -226,19 +253,23 @@ public class MVTable extends TableBase {
traceLock(session, exclusive, "added for"); traceLock(session, exclusive, "added for");
session.addLock(this); session.addLock(this);
lockExclusiveSession = session; lockExclusiveSession = session;
if (SysProperties.THREAD_DEADLOCK_DETECTOR) {
if (EXCLUSIVE_LOCKS.get() == null) { if (EXCLUSIVE_LOCKS.get() == null) {
EXCLUSIVE_LOCKS.set(new ArrayList<String>()); EXCLUSIVE_LOCKS.set(new ArrayList<String>());
} }
EXCLUSIVE_LOCKS.get().add(getName()); EXCLUSIVE_LOCKS.get().add(getName());
}
return true; return true;
} else if (lockSharedSessions.size() == 1 && } else if (lockSharedSessions.size() == 1 &&
lockSharedSessions.containsKey(session)) { lockSharedSessions.containsKey(session)) {
traceLock(session, exclusive, "add (upgraded) for "); traceLock(session, exclusive, "add (upgraded) for ");
lockExclusiveSession = session; lockExclusiveSession = session;
if (SysProperties.THREAD_DEADLOCK_DETECTOR) {
if (EXCLUSIVE_LOCKS.get() == null) { if (EXCLUSIVE_LOCKS.get() == null) {
EXCLUSIVE_LOCKS.set(new ArrayList<String>()); EXCLUSIVE_LOCKS.set(new ArrayList<String>());
} }
EXCLUSIVE_LOCKS.get().add(getName()); EXCLUSIVE_LOCKS.get().add(getName());
}
return true; return true;
} }
} }
...@@ -260,11 +291,13 @@ public class MVTable extends TableBase { ...@@ -260,11 +291,13 @@ public class MVTable extends TableBase {
traceLock(session, exclusive, "ok"); traceLock(session, exclusive, "ok");
session.addLock(this); session.addLock(this);
lockSharedSessions.put(session, session); lockSharedSessions.put(session, session);
if (SysProperties.THREAD_DEADLOCK_DETECTOR) {
if (SHARED_LOCKS.get() == null) { if (SHARED_LOCKS.get() == null) {
SHARED_LOCKS.set(new ArrayList<String>()); SHARED_LOCKS.set(new ArrayList<String>());
} }
SHARED_LOCKS.get().add(getName()); SHARED_LOCKS.get().add(getName());
} }
}
return true; return true;
} }
} }
...@@ -377,17 +410,21 @@ public class MVTable extends TableBase { ...@@ -377,17 +410,21 @@ public class MVTable extends TableBase {
traceLock(s, lockExclusiveSession == s, "unlock"); traceLock(s, lockExclusiveSession == s, "unlock");
if (lockExclusiveSession == s) { if (lockExclusiveSession == s) {
lockExclusiveSession = null; lockExclusiveSession = null;
if (SysProperties.THREAD_DEADLOCK_DETECTOR) {
if (EXCLUSIVE_LOCKS.get() != null) { if (EXCLUSIVE_LOCKS.get() != null) {
EXCLUSIVE_LOCKS.get().remove(getName()); EXCLUSIVE_LOCKS.get().remove(getName());
} }
} }
}
synchronized (getLockSyncObject()) { synchronized (getLockSyncObject()) {
if (lockSharedSessions.size() > 0) { if (lockSharedSessions.size() > 0) {
lockSharedSessions.remove(s); lockSharedSessions.remove(s);
if (SysProperties.THREAD_DEADLOCK_DETECTOR) {
if (SHARED_LOCKS.get() != null) { if (SHARED_LOCKS.get() != null) {
SHARED_LOCKS.get().remove(getName()); SHARED_LOCKS.get().remove(getName());
} }
} }
}
if (!waitingSessions.isEmpty()) { if (!waitingSessions.isEmpty()) {
getLockSyncObject().notifyAll(); getLockSyncObject().notifyAll();
} }
...@@ -849,6 +886,12 @@ public class MVTable extends TableBase { ...@@ -849,6 +886,12 @@ public class MVTable extends TableBase {
} }
} }
/**
* Convert the illegal state exception to a database exception.
*
* @param e the illegal state exception
* @return the database exception
*/
DbException convertException(IllegalStateException e) { DbException convertException(IllegalStateException e) {
if (DataUtils.getErrorCode(e.getMessage()) == if (DataUtils.getErrorCode(e.getMessage()) ==
DataUtils.ERROR_TRANSACTION_LOCKED) { DataUtils.ERROR_TRANSACTION_LOCKED) {
......
...@@ -141,6 +141,13 @@ public class MVTableEngine implements TableEngine { ...@@ -141,6 +141,13 @@ public class MVTableEngine implements TableEngine {
private String fileName; private String fileName;
/**
* Open the store for this database.
*
* @param db the database
* @param builder the builder
* @param encrypted whether the store is encrypted
*/
void open(Database db, MVStore.Builder builder, boolean encrypted) { void open(Database db, MVStore.Builder builder, boolean encrypted) {
this.encrypted = encrypted; this.encrypted = encrypted;
try { try {
...@@ -161,6 +168,13 @@ public class MVTableEngine implements TableEngine { ...@@ -161,6 +168,13 @@ public class MVTableEngine implements TableEngine {
} }
} }
/**
* Convert the illegal state exception to the correct database
* exception.
*
* @param e the illegal state exception
* @return the database exception
*/
DbException convertIllegalStateException(IllegalStateException e) { DbException convertIllegalStateException(IllegalStateException e) {
int errorCode = DataUtils.getErrorCode(e.getMessage()); int errorCode = DataUtils.getErrorCode(e.getMessage());
if (errorCode == DataUtils.ERROR_FILE_CORRUPT) { if (errorCode == DataUtils.ERROR_FILE_CORRUPT) {
......
...@@ -33,8 +33,10 @@ public class Sequence extends SchemaObjectBase { ...@@ -33,8 +33,10 @@ public class Sequence extends SchemaObjectBase {
private long maxValue; private long maxValue;
private boolean cycle; private boolean cycle;
private boolean belongsToTable; private boolean belongsToTable;
/** /**
* The last valueWithMargin we flushed. We do a little dance with this to avoid an ABBA deadlock. * The last valueWithMargin we flushed. We do a little dance with this to
* avoid an ABBA deadlock.
*/ */
private long lastFlushValueWithMargin; private long lastFlushValueWithMargin;
...@@ -289,6 +291,7 @@ public class Sequence extends SchemaObjectBase { ...@@ -289,6 +291,7 @@ public class Sequence extends SchemaObjectBase {
* Flush the current value, including the margin, to disk. * Flush the current value, including the margin, to disk.
* *
* @param session the session * @param session the session
* @param flushValueWithMargin whether to reserve more entries
*/ */
public void flush(Session session, long flushValueWithMargin) { public void flush(Session session, long flushValueWithMargin) {
if (session == null || !database.isSysTableLockedBy(session)) { if (session == null || !database.isSysTableLockedBy(session)) {
......
...@@ -202,6 +202,12 @@ public class FilePathMem extends FilePath { ...@@ -202,6 +202,12 @@ public class FilePathMem extends FilePath {
return name.equals(getScheme() + ":"); return name.equals(getScheme() + ":");
} }
/**
* Get the canonical path for this file name.
*
* @param fileName the file name
* @return the canonical path
*/
protected static String getCanonicalPath(String fileName) { protected static String getCanonicalPath(String fileName) {
fileName = fileName.replace('\\', '/'); fileName = fileName.replace('\\', '/');
int idx = fileName.indexOf(':') + 1; int idx = fileName.indexOf(':') + 1;
......
...@@ -266,7 +266,8 @@ public class Column { ...@@ -266,7 +266,8 @@ public class Column {
* @return the new or converted value * @return the new or converted value
*/ */
public Value validateConvertUpdateSequence(Session session, Value value) { public Value validateConvertUpdateSequence(Session session, Value value) {
// take a local copy of defaultExpression to avoid holding the lock while calling getValue // take a local copy of defaultExpression to avoid holding the lock
// while calling getValue
final Expression localDefaultExpression; final Expression localDefaultExpression;
synchronized (this) { synchronized (this) {
localDefaultExpression = defaultExpression; localDefaultExpression = defaultExpression;
......
...@@ -437,6 +437,7 @@ public class Server extends Tool implements Runnable, ShutdownHandler { ...@@ -437,6 +437,7 @@ public class Server extends Tool implements Runnable, ShutdownHandler {
* If no port is specified, the default port is used if possible, * If no port is specified, the default port is used if possible,
* and if this port is already used, a random port is used. * and if this port is already used, a random port is used.
* Use getPort() or getURL() after starting to retrieve the port. * Use getPort() or getURL() after starting to retrieve the port.
* </p>
* *
* @param args the argument list * @param args the argument list
* @return the server * @return the server
...@@ -463,6 +464,7 @@ public class Server extends Tool implements Runnable, ShutdownHandler { ...@@ -463,6 +464,7 @@ public class Server extends Tool implements Runnable, ShutdownHandler {
* If no port is specified, the default port is used if possible, * If no port is specified, the default port is used if possible,
* and if this port is already used, a random port is used. * and if this port is already used, a random port is used.
* Use getPort() or getURL() after starting to retrieve the port. * Use getPort() or getURL() after starting to retrieve the port.
* </p>
* *
* @param args the argument list * @param args the argument list
* @return the server * @return the server
......
...@@ -9,7 +9,7 @@ import java.util.HashMap; ...@@ -9,7 +9,7 @@ import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
* Similar to ThreadLocal, except that it allows it's data to be read from other * Similar to ThreadLocal, except that it allows its data to be read from other
* threads - useful for debugging info. * threads - useful for debugging info.
* *
* @param <T> the type * @param <T> the type
...@@ -22,6 +22,9 @@ public class DebuggingThreadLocal<T> { ...@@ -22,6 +22,9 @@ public class DebuggingThreadLocal<T> {
map.put(Thread.currentThread().getId(), value); map.put(Thread.currentThread().getId(), value);
} }
/**
* Remove the value for the current thread.
*/
public void remove() { public void remove() {
map.remove(Thread.currentThread().getId()); map.remove(Thread.currentThread().getId());
} }
...@@ -31,14 +34,12 @@ public class DebuggingThreadLocal<T> { ...@@ -31,14 +34,12 @@ public class DebuggingThreadLocal<T> {
} }
/** /**
* Get a snapshot of the data of all threads.
*
* @return a HashMap containing a mapping from thread-id to value * @return a HashMap containing a mapping from thread-id to value
*/ */
public HashMap<Long, T> getSnapshotOfAllThreads() { public HashMap<Long, T> getSnapshotOfAllThreads() {
return new HashMap<Long, T>(map); return new HashMap<Long, T>(map);
} }
public T deepCopy(T value) {
return value;
}
} }
...@@ -20,8 +20,8 @@ import java.util.TimerTask; ...@@ -20,8 +20,8 @@ import java.util.TimerTask;
import org.h2.mvstore.db.MVTable; import org.h2.mvstore.db.MVTable;
/** /**
* Detects deadlocks between threads. Prints out data in the same format as the CTRL-BREAK handler, * Detects deadlocks between threads. Prints out data in the same format as the
* but includes information about table locks. * CTRL-BREAK handler, but includes information about table locks.
*/ */
public class ThreadDeadlockDetector { public class ThreadDeadlockDetector {
...@@ -29,13 +29,13 @@ public class ThreadDeadlockDetector { ...@@ -29,13 +29,13 @@ public class ThreadDeadlockDetector {
private static ThreadDeadlockDetector detector; private static ThreadDeadlockDetector detector;
private final ThreadMXBean tmbean; private final ThreadMXBean threadBean;
// a daemon thread // a daemon thread
private final Timer threadCheck = new Timer("ThreadDeadlockDetector", true); private final Timer threadCheck = new Timer("ThreadDeadlockDetector", true);
private ThreadDeadlockDetector() { private ThreadDeadlockDetector() {
this.tmbean = ManagementFactory.getThreadMXBean(); this.threadBean = ManagementFactory.getThreadMXBean();
// delay: 10 ms // delay: 10 ms
// period: 10000 ms (100 seconds) // period: 10000 ms (100 seconds)
threadCheck.schedule(new TimerTask() { threadCheck.schedule(new TimerTask() {
...@@ -46,6 +46,9 @@ public class ThreadDeadlockDetector { ...@@ -46,6 +46,9 @@ public class ThreadDeadlockDetector {
}, 10, 10000); }, 10, 10000);
} }
/**
* Initialize the detector.
*/
public static synchronized void init() { public static synchronized void init() {
if (detector == null) { if (detector == null) {
detector = new ThreadDeadlockDetector(); detector = new ThreadDeadlockDetector();
...@@ -53,11 +56,12 @@ public class ThreadDeadlockDetector { ...@@ -53,11 +56,12 @@ public class ThreadDeadlockDetector {
} }
/** /**
* Checks if any threads are deadlocked. If any, print the thread dump information. * Checks if any threads are deadlocked. If any, print the thread dump
* information.
*/ */
void checkForDeadlocks() { void checkForDeadlocks() {
long[] tids = tmbean.findDeadlockedThreads(); long[] ids = threadBean.findDeadlockedThreads();
if (tids == null) { if (ids == null) {
return; return;
} }
...@@ -65,24 +69,24 @@ public class ThreadDeadlockDetector { ...@@ -65,24 +69,24 @@ public class ThreadDeadlockDetector {
final PrintWriter print = new PrintWriter(stringWriter); final PrintWriter print = new PrintWriter(stringWriter);
print.println("ThreadDeadlockDetector - deadlock found :"); print.println("ThreadDeadlockDetector - deadlock found :");
final ThreadInfo[] infos = tmbean.getThreadInfo(tids, true, true); final ThreadInfo[] infos = threadBean.getThreadInfo(ids, true, true);
final HashMap<Long, String> mvtableWaitingForLockMap = final HashMap<Long, String> tableWaitingForLockMap =
MVTable.WAITING_FOR_LOCK.getSnapshotOfAllThreads(); MVTable.WAITING_FOR_LOCK.getSnapshotOfAllThreads();
final HashMap<Long, ArrayList<String>> mvtableExclusiveLocksMap = final HashMap<Long, ArrayList<String>> tableExclusiveLocksMap =
MVTable.EXCLUSIVE_LOCKS.getSnapshotOfAllThreads(); MVTable.EXCLUSIVE_LOCKS.getSnapshotOfAllThreads();
final HashMap<Long, ArrayList<String>> mvtableSharedLocksMap = final HashMap<Long, ArrayList<String>> tableSharedLocksMap =
MVTable.SHARED_LOCKS.getSnapshotOfAllThreads(); MVTable.SHARED_LOCKS.getSnapshotOfAllThreads();
for (ThreadInfo ti : infos) { for (ThreadInfo ti : infos) {
printThreadInfo(print, ti); printThreadInfo(print, ti);
printLockInfo(print, ti.getLockedSynchronizers(), printLockInfo(print, ti.getLockedSynchronizers(),
mvtableWaitingForLockMap.get(ti.getThreadId()), tableWaitingForLockMap.get(ti.getThreadId()),
mvtableExclusiveLocksMap.get(ti.getThreadId()), tableExclusiveLocksMap.get(ti.getThreadId()),
mvtableSharedLocksMap.get(ti.getThreadId())); tableSharedLocksMap.get(ti.getThreadId()));
} }
print.flush(); print.flush();
// Dump it to system.out in one block, so it doesn't get mixed up with other stuff when we're // Dump it to system.out in one block, so it doesn't get mixed up with
// using a logging subsystem. // other stuff when we're using a logging subsystem.
System.out.println(stringWriter.getBuffer()); System.out.println(stringWriter.getBuffer());
} }
...@@ -91,11 +95,11 @@ public class ThreadDeadlockDetector { ...@@ -91,11 +95,11 @@ public class ThreadDeadlockDetector {
printThread(print, ti); printThread(print, ti);
// print stack trace with locks // print stack trace with locks
StackTraceElement[] stacktrace = ti.getStackTrace(); StackTraceElement[] stackTrace = ti.getStackTrace();
MonitorInfo[] monitors = ti.getLockedMonitors(); MonitorInfo[] monitors = ti.getLockedMonitors();
for (int i = 0; i < stacktrace.length; i++) { for (int i = 0; i < stackTrace.length; i++) {
StackTraceElement ste = stacktrace[i]; StackTraceElement e = stackTrace[i];
print.println(INDENT + "at " + ste.toString()); print.println(INDENT + "at " + e.toString());
for (MonitorInfo mi : monitors) { for (MonitorInfo mi : monitors) {
if (mi.getLockedStackDepth() == i) { if (mi.getLockedStackDepth() == i) {
print.println(INDENT + " - locked " + mi); print.println(INDENT + " - locked " + mi);
...@@ -125,25 +129,25 @@ public class ThreadDeadlockDetector { ...@@ -125,25 +129,25 @@ public class ThreadDeadlockDetector {
} }
private static void printLockInfo(PrintWriter print, LockInfo[] locks, private static void printLockInfo(PrintWriter print, LockInfo[] locks,
String mvtableWaitingForLock, String tableWaitingForLock,
ArrayList<String> mvtableExclusiveLocks, ArrayList<String> tableExclusiveLocks,
ArrayList<String> mvtableSharedLocksMap) { ArrayList<String> tableSharedLocksMap) {
print.println(INDENT + "Locked synchronizers: count = " + locks.length); print.println(INDENT + "Locked synchronizers: count = " + locks.length);
for (LockInfo li : locks) { for (LockInfo li : locks) {
print.println(INDENT + " - " + li); print.println(INDENT + " - " + li);
} }
if (mvtableWaitingForLock != null) { if (tableWaitingForLock != null) {
print.println(INDENT + "Waiting for table: " + mvtableWaitingForLock); print.println(INDENT + "Waiting for table: " + tableWaitingForLock);
} }
if (mvtableExclusiveLocks != null) { if (tableExclusiveLocks != null) {
print.println(INDENT + "Exclusive table locks: count = " + mvtableExclusiveLocks.size()); print.println(INDENT + "Exclusive table locks: count = " + tableExclusiveLocks.size());
for (String name : mvtableExclusiveLocks) { for (String name : tableExclusiveLocks) {
print.println(INDENT + " - " + name); print.println(INDENT + " - " + name);
} }
} }
if (mvtableSharedLocksMap != null) { if (tableSharedLocksMap != null) {
print.println(INDENT + "Shared table locks: count = " + mvtableSharedLocksMap.size()); print.println(INDENT + "Shared table locks: count = " + tableSharedLocksMap.size());
for (String name : mvtableSharedLocksMap) { for (String name : tableSharedLocksMap) {
print.println(INDENT + " - " + name); print.println(INDENT + " - " + name);
} }
} }
......
...@@ -1005,7 +1005,7 @@ public abstract class TestBase { ...@@ -1005,7 +1005,7 @@ public abstract class TestBase {
/** /**
* Check that executing the specified query results in the specified error. * Check that executing the specified query results in the specified error.
* *
* @param expectedErrorMessage the expected error message * @param expectedErrorCode the expected error code
* @param stat the statement * @param stat the statement
* @param sql the SQL statement to execute * @param sql the SQL statement to execute
*/ */
......
...@@ -130,8 +130,10 @@ public class TestCompatibilityOracle extends TestBase { ...@@ -130,8 +130,10 @@ public class TestCompatibilityOracle extends TestBase {
Connection conn = getConnection("oracle;MODE=Oracle"); Connection conn = getConnection("oracle;MODE=Oracle");
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, U##NAME VARCHAR(255))"); stat.execute(
stat.execute("INSERT INTO TEST VALUES(1, 'Hello'), (2, 'HelloWorld'), (3, 'HelloWorldWorld')"); "CREATE TABLE TEST(ID INT PRIMARY KEY, U##NAME VARCHAR(255))");
stat.execute(
"INSERT INTO TEST VALUES(1, 'Hello'), (2, 'HelloWorld'), (3, 'HelloWorldWorld')");
assertResult("1", stat, "SELECT ID FROM TEST where U##NAME ='Hello'"); assertResult("1", stat, "SELECT ID FROM TEST where U##NAME ='Hello'");
......
...@@ -1227,7 +1227,8 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1227,7 +1227,8 @@ public class TestFunctions extends TestBase implements AggregateFunction {
Connection conn = getConnection("functions"); Connection conn = getConnection("functions");
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
String testStr = "foo"; String testStr = "foo";
assertResult(String.valueOf("foo".hashCode()), stat, String.format("SELECT ORA_HASH('%s') FROM DUAL", testStr)); assertResult(String.valueOf("foo".hashCode()), stat,
String.format("SELECT ORA_HASH('%s') FROM DUAL", testStr));
assertResult(String.valueOf("foo".hashCode()), stat, assertResult(String.valueOf("foo".hashCode()), stat,
String.format("SELECT ORA_HASH('%s', 0) FROM DUAL", testStr)); String.format("SELECT ORA_HASH('%s', 0) FROM DUAL", testStr));
assertResult(String.valueOf("foo".hashCode()), stat, assertResult(String.valueOf("foo".hashCode()), stat,
...@@ -1327,8 +1328,6 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1327,8 +1328,6 @@ public class TestFunctions extends TestBase implements AggregateFunction {
Timestamp.valueOf("1979-11-12 08:12:34.560")); Timestamp.valueOf("1979-11-12 08:12:34.560"));
assertResult(expected, stat, assertResult(expected, stat,
"SELECT TO_CHAR(X, 'DL') FROM T"); "SELECT TO_CHAR(X, 'DL') FROM T");
// assertResult("Monday, November 12, 1979", stat,
// "SELECT TO_CHAR(X, 'DL', 'NLS_DATE_LANGUAGE = English') FROM T");
assertResult("11/12/1979", stat, "SELECT TO_CHAR(X, 'DS') FROM T"); assertResult("11/12/1979", stat, "SELECT TO_CHAR(X, 'DS') FROM T");
assertResult("11/12/1979", stat, "SELECT TO_CHAR(X, 'Ds') FROM T"); assertResult("11/12/1979", stat, "SELECT TO_CHAR(X, 'Ds') FROM T");
assertResult("11/12/1979", stat, "SELECT TO_CHAR(X, 'dS') FROM T"); assertResult("11/12/1979", stat, "SELECT TO_CHAR(X, 'dS') FROM T");
......
...@@ -59,7 +59,7 @@ public class TestReorderWrites extends TestBase { ...@@ -59,7 +59,7 @@ public class TestReorderWrites extends TestBase {
store.commit(); store.commit();
store.getFileStore().sync(); store.getFileStore().sync();
int stop = 4 + r.nextInt(20); int stop = 4 + r.nextInt(20);
log("synched start"); log("countdown start");
fs.setPowerOffCountdown(stop, i); fs.setPowerOffCountdown(stop, i);
try { try {
for (int j = 1; j < 100; j++) { for (int j = 1; j < 100; j++) {
......
...@@ -25,6 +25,9 @@ import org.h2.util.IOUtils; ...@@ -25,6 +25,9 @@ import org.h2.util.IOUtils;
*/ */
public class FilePathReorderWrites extends FilePathWrapper { public class FilePathReorderWrites extends FilePathWrapper {
/**
* Whether trace output of all method calls is enabled.
*/
static final boolean TRACE = false; static final boolean TRACE = false;
private static final FilePathReorderWrites INSTANCE = new FilePathReorderWrites(); private static final FilePathReorderWrites INSTANCE = new FilePathReorderWrites();
...@@ -48,8 +51,8 @@ public class FilePathReorderWrites extends FilePathWrapper { ...@@ -48,8 +51,8 @@ public class FilePathReorderWrites extends FilePathWrapper {
} }
/** /**
* Set the number of write operations before a simulated power failure, and the * Set the number of write operations before a simulated power failure, and
* random seed (for partial writes). * the random seed (for partial writes).
* *
* @param count the number of write operations (0 to never fail, * @param count the number of write operations (0 to never fail,
* Integer.MAX_VALUE to count the operations) * Integer.MAX_VALUE to count the operations)
...@@ -150,7 +153,8 @@ class FileReorderWrites extends FileBase { ...@@ -150,7 +153,8 @@ class FileReorderWrites extends FileBase {
private final FileChannel base; private final FileChannel base;
/** /**
* The base channel that is used for reading, where all operations are immediately applied to get a consistent view before a power failure. * The base channel that is used for reading, where all operations are
* immediately applied to get a consistent view before a power failure.
*/ */
private final FileChannel readBase; private final FileChannel readBase;
...@@ -216,12 +220,12 @@ class FileReorderWrites extends FileBase { ...@@ -216,12 +220,12 @@ class FileReorderWrites extends FileBase {
trace("op " + op); trace("op " + op);
checkError(); checkError();
notAppliedList.add(op); notAppliedList.add(op);
long now = op.time; long now = op.getTime();
for (int i = 0; i < notAppliedList.size() - 1; i++) { for (int i = 0; i < notAppliedList.size() - 1; i++) {
FileOperation old = notAppliedList.get(i); FileOperation old = notAppliedList.get(i);
boolean applyOld = false; boolean applyOld = false;
// String reason = ""; // String reason = "";
if (old.time + 45000 < now) { if (old.getTime() + 45000 < now) {
// reason = "old"; // reason = "old";
applyOld = true; applyOld = true;
} else if (old.overlaps(op)) { } else if (old.overlaps(op)) {
...@@ -295,10 +299,10 @@ class FileReorderWrites extends FileBase { ...@@ -295,10 +299,10 @@ class FileReorderWrites extends FileBase {
* be applied on power failure). * be applied on power failure).
*/ */
static class FileOperation { static class FileOperation {
final int id; private final int id;
final long time; private final long time;
final ByteBuffer buffer; private final ByteBuffer buffer;
final long position; private final long position;
FileOperation(int id, long position, ByteBuffer src) { FileOperation(int id, long position, ByteBuffer src) {
this.id = id; this.id = id;
...@@ -314,7 +318,18 @@ class FileReorderWrites extends FileBase { ...@@ -314,7 +318,18 @@ class FileReorderWrites extends FileBase {
this.position = position; this.position = position;
} }
public boolean overlaps(FileOperation other) { public long getTime() {
return time;
}
/**
* Check whether the file region of this operation overlaps with
* another operation.
*
* @param other the other operation
* @return if there is an overlap
*/
boolean overlaps(FileOperation other) {
if (isTruncate() && other.isTruncate()) { if (isTruncate() && other.isTruncate()) {
// we just keep the latest truncate operation // we just keep the latest truncate operation
return true; return true;
...@@ -340,6 +355,12 @@ class FileReorderWrites extends FileBase { ...@@ -340,6 +355,12 @@ class FileReorderWrites extends FileBase {
return buffer == null ? 0 : buffer.limit() - buffer.position(); return buffer == null ? 0 : buffer.limit() - buffer.position();
} }
/**
* Apply the operation to the channel.
*
* @param channel the channel
* @return the return value of the operation
*/
int apply(FileChannel channel) throws IOException { int apply(FileChannel channel) throws IOException {
if (isTruncate()) { if (isTruncate()) {
channel.truncate(position); channel.truncate(position);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论