mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
GP-2183 corrected potential deadlock with various DBListener callbacks
This commit is contained in:
parent
0e3fe30c67
commit
222850142a
7 changed files with 323 additions and 90 deletions
|
@ -47,6 +47,7 @@ public class DBHandle {
|
||||||
private long lastTransactionID;
|
private long lastTransactionID;
|
||||||
private boolean txStarted = false;
|
private boolean txStarted = false;
|
||||||
private boolean waitingForNewTransaction = false;
|
private boolean waitingForNewTransaction = false;
|
||||||
|
private boolean reloadInProgress = false;
|
||||||
|
|
||||||
private long checkpointNum;
|
private long checkpointNum;
|
||||||
private long lastRecoverySnapshotId;
|
private long lastRecoverySnapshotId;
|
||||||
|
@ -316,8 +317,10 @@ public class DBHandle {
|
||||||
* Close the scratch-pad database handle if it open.
|
* Close the scratch-pad database handle if it open.
|
||||||
*/
|
*/
|
||||||
public void closeScratchPad() {
|
public void closeScratchPad() {
|
||||||
if (scratchPad != null) {
|
// use copy of scratchPad to be thread-safe
|
||||||
scratchPad.close();
|
DBHandle scratchDbh = scratchPad;
|
||||||
|
if (scratchDbh != null) {
|
||||||
|
scratchDbh.close();
|
||||||
scratchPad = null;
|
scratchPad = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -330,25 +333,31 @@ public class DBHandle {
|
||||||
listenerList.add(listener);
|
listenerList.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dbRestored() {
|
private void notifyDbRestored() {
|
||||||
for (DBListener listener : listenerList) {
|
for (DBListener listener : listenerList) {
|
||||||
listener.dbRestored(this);
|
listener.dbRestored(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dbClosed() {
|
private void notifyDbClosed() {
|
||||||
for (DBListener listener : listenerList) {
|
for (DBListener listener : listenerList) {
|
||||||
listener.dbClosed(this);
|
listener.dbClosed(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tableAdded(Table table) {
|
private void notifyTableAdded(Table table) {
|
||||||
|
if (reloadInProgress) {
|
||||||
|
return; // squash notification during reload (e.g., undo/redo)
|
||||||
|
}
|
||||||
for (DBListener listener : listenerList) {
|
for (DBListener listener : listenerList) {
|
||||||
listener.tableAdded(this, table);
|
listener.tableAdded(this, table);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void tableDeleted(Table table) {
|
void notifyTableDeleted(Table table) {
|
||||||
|
if (reloadInProgress) {
|
||||||
|
return; // squash notification during reload (e.g., undo/redo)
|
||||||
|
}
|
||||||
for (DBListener listener : listenerList) {
|
for (DBListener listener : listenerList) {
|
||||||
listener.tableDeleted(this, table);
|
listener.tableDeleted(this, table);
|
||||||
}
|
}
|
||||||
|
@ -418,15 +427,39 @@ public class DBHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Terminate transaction. If commit is false, Table instances may be added
|
* End current transaction. If commit is false a rollback may occur followed by
|
||||||
* or removed/invalidated.
|
* {@link DBListener#dbRestored(DBHandle)} notification to listeners.
|
||||||
|
*
|
||||||
* @param id transaction ID
|
* @param id transaction ID
|
||||||
* @param commit if true a new checkpoint will be established, if
|
* @param commit if true a new checkpoint will be established for active transaction, if
|
||||||
* false all changes since the previous checkpoint will be discarded.
|
* false all changes since the previous checkpoint will be discarded.
|
||||||
* @return true if new checkpoint established.
|
* @return true if new checkpoint established, false if nothing to commit
|
||||||
|
* or commit parameter specified as false and active transaction is terminated with rollback.
|
||||||
* @throws IOException if IO error occurs
|
* @throws IOException if IO error occurs
|
||||||
*/
|
*/
|
||||||
public synchronized boolean endTransaction(long id, boolean commit) throws IOException {
|
public boolean endTransaction(long id, boolean commit) throws IOException {
|
||||||
|
try {
|
||||||
|
return doEndTransaction(id, commit);
|
||||||
|
}
|
||||||
|
catch (DBRollbackException e) {
|
||||||
|
notifyDbRestored();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End current transaction. If <code>commit</code> is false a rollback may be perfromed.
|
||||||
|
*
|
||||||
|
* @param id transaction ID
|
||||||
|
* @param commit if true a new checkpoint will be established for active transaction, if
|
||||||
|
* false all changes since the previous checkpoint will be discarded.
|
||||||
|
* @return true if new checkpoint established.
|
||||||
|
* @throws DBRollbackException if <code>commit</code> is false and active transaction was
|
||||||
|
* terminated and database rollback was performed (i.e., undo performed).
|
||||||
|
* @throws IOException if IO error occurs
|
||||||
|
*/
|
||||||
|
private synchronized boolean doEndTransaction(long id, boolean commit)
|
||||||
|
throws DBRollbackException, IOException {
|
||||||
if (id != lastTransactionID) {
|
if (id != lastTransactionID) {
|
||||||
throw new IllegalStateException("Transaction id is not active");
|
throw new IllegalStateException("Transaction id is not active");
|
||||||
}
|
}
|
||||||
|
@ -440,9 +473,11 @@ public class DBHandle {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// rollback
|
|
||||||
|
// rollback transaction
|
||||||
bufferMgr.undo(false);
|
bufferMgr.undo(false);
|
||||||
reloadTables();
|
reloadTables();
|
||||||
|
throw new DBRollbackException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
@ -459,9 +494,33 @@ public class DBHandle {
|
||||||
return (bufferMgr != null && !bufferMgr.atCheckpoint());
|
return (bufferMgr != null && !bufferMgr.atCheckpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void terminateTransaction(long id, boolean commit) throws IOException {
|
/**
|
||||||
endTransaction(id, commit);
|
* Terminate current transaction. If commit is false a rollback may occur followed by
|
||||||
waitingForNewTransaction = true;
|
* {@link DBListener#dbRestored(DBHandle)} notification to listeners. This method is very
|
||||||
|
* similar to {@link #endTransaction(long, boolean)} with the added behavior of setting the
|
||||||
|
* internal {@link DBHandle} state such that any subsequent invocations of
|
||||||
|
* {@link #checkTransaction()} will throw a {@link TerminatedTransactionException} until a new
|
||||||
|
* transaction is started.
|
||||||
|
*
|
||||||
|
* @param id transaction ID
|
||||||
|
* @param commit if true a new checkpoint will be established for active transaction, if
|
||||||
|
* false all changes since the previous checkpoint will be discarded.
|
||||||
|
* @throws IOException if IO error occurs
|
||||||
|
*/
|
||||||
|
public void terminateTransaction(long id, boolean commit) throws IOException {
|
||||||
|
boolean rollback = false;
|
||||||
|
synchronized (this) {
|
||||||
|
try {
|
||||||
|
doEndTransaction(id, commit);
|
||||||
|
}
|
||||||
|
catch (DBRollbackException e) {
|
||||||
|
rollback = true;
|
||||||
|
}
|
||||||
|
waitingForNewTransaction = true;
|
||||||
|
}
|
||||||
|
if (rollback) {
|
||||||
|
notifyDbRestored();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -476,16 +535,22 @@ public class DBHandle {
|
||||||
* Undo changes made during the previous transaction checkpoint.
|
* Undo changes made during the previous transaction checkpoint.
|
||||||
* All upper-levels must clear table-based cached data prior to
|
* All upper-levels must clear table-based cached data prior to
|
||||||
* invoking this method.
|
* invoking this method.
|
||||||
* @return true if an undo was successful
|
* @return true if an undo was successful, else false if not allowed
|
||||||
* @throws IOException if IO error occurs
|
* @throws IOException if IO error occurs
|
||||||
*/
|
*/
|
||||||
public synchronized boolean undo() throws IOException {
|
public boolean undo() throws IOException {
|
||||||
if (canUndo() && bufferMgr.undo(true)) {
|
boolean success = false;
|
||||||
++checkpointNum;
|
synchronized (this) {
|
||||||
reloadTables();
|
if (canUndo() && bufferMgr.undo(true)) {
|
||||||
return true;
|
++checkpointNum;
|
||||||
|
reloadTables();
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
if (success) {
|
||||||
|
notifyDbRestored();
|
||||||
|
}
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -515,16 +580,22 @@ public class DBHandle {
|
||||||
* Moves forward by one checkpoint only.
|
* Moves forward by one checkpoint only.
|
||||||
* All upper-levels must clear table-based cached data prior to
|
* All upper-levels must clear table-based cached data prior to
|
||||||
* invoking this method.
|
* invoking this method.
|
||||||
* @return boolean
|
* @return boolean if redo is successful, else false if undo not allowed
|
||||||
* @throws IOException if IO error occurs
|
* @throws IOException if IO error occurs
|
||||||
*/
|
*/
|
||||||
public synchronized boolean redo() throws IOException {
|
public boolean redo() throws IOException {
|
||||||
if (canRedo() && bufferMgr.redo()) {
|
boolean success = false;
|
||||||
++checkpointNum;
|
synchronized (this) {
|
||||||
reloadTables();
|
if (canRedo() && bufferMgr.redo()) {
|
||||||
return true;
|
++checkpointNum;
|
||||||
|
reloadTables();
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
if (success) {
|
||||||
|
notifyDbRestored();
|
||||||
|
}
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -569,7 +640,7 @@ public class DBHandle {
|
||||||
* Close the database and dispose of the underlying buffer manager.
|
* Close the database and dispose of the underlying buffer manager.
|
||||||
* Any existing recovery data will be discarded.
|
* Any existing recovery data will be discarded.
|
||||||
*/
|
*/
|
||||||
public synchronized void close() {
|
public void close() {
|
||||||
close(false);
|
close(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -578,11 +649,15 @@ public class DBHandle {
|
||||||
* @param keepRecoveryData true if existing recovery data should be retained or false to remove
|
* @param keepRecoveryData true if existing recovery data should be retained or false to remove
|
||||||
* any recovery data
|
* any recovery data
|
||||||
*/
|
*/
|
||||||
public synchronized void close(boolean keepRecoveryData) {
|
public void close(boolean keepRecoveryData) {
|
||||||
|
|
||||||
closeScratchPad();
|
closeScratchPad();
|
||||||
if (bufferMgr != null) {
|
|
||||||
dbClosed();
|
// use copy of bufferMgr to be thread-safe
|
||||||
bufferMgr.dispose(keepRecoveryData);
|
BufferMgr mgr = bufferMgr;
|
||||||
|
if (mgr != null) {
|
||||||
|
notifyDbClosed();
|
||||||
|
mgr.dispose(keepRecoveryData);
|
||||||
bufferMgr = null;
|
bufferMgr = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -844,32 +919,38 @@ public class DBHandle {
|
||||||
*/
|
*/
|
||||||
private void reloadTables() throws IOException {
|
private void reloadTables() throws IOException {
|
||||||
|
|
||||||
dbParms.refresh();
|
reloadInProgress = true;
|
||||||
|
try {
|
||||||
|
dbParms.refresh();
|
||||||
|
|
||||||
Hashtable<String, Table> oldTables = tables;
|
Hashtable<String, Table> oldTables = tables;
|
||||||
tables = new Hashtable<>();
|
tables = new Hashtable<>();
|
||||||
TableRecord[] tableRecords = masterTable.refreshTableRecords();
|
// NOTE: master table invalidates any obsolete tables during refresh
|
||||||
for (TableRecord tableRecord : tableRecords) {
|
TableRecord[] tableRecords = masterTable.refreshTableRecords();
|
||||||
|
for (TableRecord tableRecord : tableRecords) {
|
||||||
|
|
||||||
String tableName = tableRecord.getName();
|
String tableName = tableRecord.getName();
|
||||||
|
|
||||||
// Process each primary tables
|
// Process each primary tables
|
||||||
if (tableRecord.getIndexedColumn() < 0) {
|
if (tableRecord.getIndexedColumn() < 0) {
|
||||||
Table t = oldTables.get(tableName);
|
Table t = oldTables.get(tableName);
|
||||||
if (t == null || t.isInvalid()) {
|
if (t == null || t.isInvalid()) {
|
||||||
oldTables.remove(tableName);
|
oldTables.remove(tableName);
|
||||||
t = new Table(this, tableRecord);
|
t = new Table(this, tableRecord);
|
||||||
tableAdded(t);
|
notifyTableAdded(t);
|
||||||
|
}
|
||||||
|
tables.put(tableName, t);
|
||||||
}
|
}
|
||||||
tables.put(tableName, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
// secondary table indexes
|
// secondary table indexes
|
||||||
else if (!oldTables.containsKey(tableName)) {
|
else if (!oldTables.containsKey(tableName)) {
|
||||||
IndexTable.getIndexTable(this, tableRecord);
|
IndexTable.getIndexTable(this, tableRecord);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dbRestored();
|
finally {
|
||||||
|
reloadInProgress = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -917,20 +998,23 @@ public class DBHandle {
|
||||||
* @return new table instance
|
* @return new table instance
|
||||||
* @throws IOException if IO error occurs during table creation
|
* @throws IOException if IO error occurs during table creation
|
||||||
*/
|
*/
|
||||||
public synchronized Table createTable(String name, Schema schema, int[] indexedColumns)
|
public Table createTable(String name, Schema schema, int[] indexedColumns)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (tables.containsKey(name)) {
|
Table table;
|
||||||
throw new IOException("Table already exists");
|
synchronized (this) {
|
||||||
}
|
if (tables.containsKey(name)) {
|
||||||
checkTransaction();
|
throw new IOException("Table already exists");
|
||||||
Table table = new Table(this, masterTable.createTableRecord(name, schema, -1));
|
}
|
||||||
tables.put(name, table);
|
checkTransaction();
|
||||||
if (indexedColumns != null) {
|
table = new Table(this, masterTable.createTableRecord(name, schema, -1));
|
||||||
for (int indexedColumn : indexedColumns) {
|
tables.put(name, table);
|
||||||
IndexTable.createIndexTable(table, indexedColumn);
|
if (indexedColumns != null) {
|
||||||
|
for (int indexedColumn : indexedColumns) {
|
||||||
|
IndexTable.createIndexTable(table, indexedColumn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tableAdded(table);
|
notifyTableAdded(table);
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -964,19 +1048,23 @@ public class DBHandle {
|
||||||
* @param name table name
|
* @param name table name
|
||||||
* @throws IOException if there is an I/O error or the table does not exist
|
* @throws IOException if there is an I/O error or the table does not exist
|
||||||
*/
|
*/
|
||||||
public synchronized void deleteTable(String name) throws IOException {
|
public void deleteTable(String name) throws IOException {
|
||||||
Table table = tables.get(name);
|
Table table;
|
||||||
if (table == null) {
|
synchronized (this) {
|
||||||
return;
|
table = tables.get(name);
|
||||||
|
if (table == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
checkTransaction();
|
||||||
|
int[] indexedColumns = table.getIndexedColumns();
|
||||||
|
for (int indexedColumn : indexedColumns) {
|
||||||
|
table.removeIndex(indexedColumn);
|
||||||
|
}
|
||||||
|
table.deleteAll();
|
||||||
|
masterTable.deleteTableRecord(table.getTableNum());
|
||||||
|
tables.remove(name);
|
||||||
}
|
}
|
||||||
checkTransaction();
|
notifyTableDeleted(table);
|
||||||
int[] indexedColumns = table.getIndexedColumns();
|
|
||||||
for (int indexedColumn : indexedColumns) {
|
|
||||||
table.removeIndex(indexedColumn);
|
|
||||||
}
|
|
||||||
table.deleteAll();
|
|
||||||
masterTable.deleteTableRecord(table.getTableNum());
|
|
||||||
tables.remove(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1000,9 +1088,6 @@ public class DBHandle {
|
||||||
return bufferMgr.getLowBufferCount();
|
return bufferMgr.getLowBufferCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @see java.lang.Object#finalize()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
protected void finalize() throws Throwable {
|
protected void finalize() throws Throwable {
|
||||||
close(true);
|
close(true);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -23,9 +22,9 @@ public interface DBListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides notification that an undo or redo was performed.
|
* Provides notification that an undo or redo was performed.
|
||||||
* Separate notification will be provided if tables were added/removed.
|
* During the restore process {@link #tableAdded(DBHandle, Table)} and
|
||||||
* The state of the database may still be in transition and should not be accessed
|
* {@link #tableDeleted(DBHandle, Table)} notifications will be supressed.
|
||||||
* by this callback method.
|
* Any listener concerned with tables added or removed should reacquire their table(s).
|
||||||
* @param dbh associated database handle
|
* @param dbh associated database handle
|
||||||
*/
|
*/
|
||||||
void dbRestored(DBHandle dbh);
|
void dbRestored(DBHandle dbh);
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <code>DBRollbackException</code> thrown when a database transaction rollback was performed
|
||||||
|
* during transaction termination.
|
||||||
|
*/
|
||||||
|
class DBRollbackException extends Exception {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct exception
|
||||||
|
*/
|
||||||
|
DBRollbackException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
|
@ -112,14 +112,10 @@ public class Table {
|
||||||
* Subsequent table use may generate an exception.
|
* Subsequent table use may generate an exception.
|
||||||
*/
|
*/
|
||||||
void invalidate() {
|
void invalidate() {
|
||||||
boolean isIndexTable = tableRecord.getIndexedColumn() >= 0;
|
|
||||||
tableRecord = null;
|
tableRecord = null;
|
||||||
rootBufferId = -1;
|
rootBufferId = -1;
|
||||||
nodeMgr = null;
|
nodeMgr = null;
|
||||||
++modCount;
|
++modCount;
|
||||||
if (!isIndexTable) {
|
|
||||||
db.tableDeleted(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,7 +19,7 @@ import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.*;
|
||||||
|
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
|
@ -46,12 +46,15 @@ public class DBTest extends AbstractGenericTest {
|
||||||
private BufferFileManager fileMgr;
|
private BufferFileManager fileMgr;
|
||||||
private DBHandle dbh;
|
private DBHandle dbh;
|
||||||
private BufferFile bfile;
|
private BufferFile bfile;
|
||||||
|
private MyDbListener listener;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
|
|
||||||
testDir = createTempDirectory(getClass().getSimpleName());
|
testDir = createTempDirectory(getClass().getSimpleName());
|
||||||
dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
|
dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
|
||||||
|
listener = new MyDbListener();
|
||||||
|
dbh.addListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -548,4 +551,108 @@ public class DBTest extends AbstractGenericTest {
|
||||||
assertNotNull(t1prime.getRecord(1));
|
assertNotNull(t1prime.getRecord(1));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEvents() throws IOException {
|
||||||
|
|
||||||
|
createIndexedTables(false);
|
||||||
|
|
||||||
|
// Verify table added events
|
||||||
|
Table[] tables = dbh.getTables();
|
||||||
|
assertEquals(11, tables.length);
|
||||||
|
assertEquals(tables.length, listener.events.size());
|
||||||
|
int ix = 0;
|
||||||
|
for (DbEvent evt : listener.events) {
|
||||||
|
assertEquals(DbNotifyType.TABLE_ADDED, evt.type);
|
||||||
|
assertEquals("TABLE" + ix, evt.table.getName());
|
||||||
|
++ix;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.events.clear();
|
||||||
|
|
||||||
|
// Verify table deleted event
|
||||||
|
long txId = dbh.startTransaction();
|
||||||
|
Table t = dbh.getTable("TABLE5");
|
||||||
|
dbh.deleteTable("TABLE5");
|
||||||
|
dbh.endTransaction(txId, true);
|
||||||
|
assertEquals(1, listener.events.size());
|
||||||
|
DbEvent evt = listener.events.get(0);
|
||||||
|
assertEquals(DbNotifyType.TABLE_DELETED, evt.type);
|
||||||
|
assertTrue(t == evt.table);
|
||||||
|
|
||||||
|
listener.events.clear();
|
||||||
|
|
||||||
|
dbh.undo();
|
||||||
|
|
||||||
|
assertEquals(1, listener.events.size());
|
||||||
|
evt = listener.events.get(0);
|
||||||
|
assertEquals(DbNotifyType.RESTORED, evt.type);
|
||||||
|
|
||||||
|
listener.events.clear();
|
||||||
|
|
||||||
|
dbh.redo();
|
||||||
|
|
||||||
|
assertEquals(1, listener.events.size());
|
||||||
|
evt = listener.events.get(0);
|
||||||
|
assertEquals(DbNotifyType.RESTORED, evt.type);
|
||||||
|
|
||||||
|
listener.events.clear();
|
||||||
|
|
||||||
|
dbh.close();
|
||||||
|
dbh = null;
|
||||||
|
|
||||||
|
assertEquals(1, listener.events.size());
|
||||||
|
evt = listener.events.get(0);
|
||||||
|
assertEquals(DbNotifyType.CLOSED, evt.type);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum DbNotifyType {
|
||||||
|
RESTORED, CLOSED, TABLE_DELETED, TABLE_ADDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DbEvent {
|
||||||
|
|
||||||
|
final DbNotifyType type;
|
||||||
|
final Table table;
|
||||||
|
|
||||||
|
DbEvent(DbNotifyType type, Table table) {
|
||||||
|
this.type = type;
|
||||||
|
this.table = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (type != DbNotifyType.TABLE_ADDED) {
|
||||||
|
return type.toString();
|
||||||
|
}
|
||||||
|
return type + ": " + table.getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MyDbListener implements DBListener {
|
||||||
|
|
||||||
|
private List<DbEvent> events = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dbRestored(DBHandle dbh) {
|
||||||
|
events.add(new DbEvent(DbNotifyType.RESTORED, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dbClosed(DBHandle dbh) {
|
||||||
|
events.add(new DbEvent(DbNotifyType.CLOSED, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tableDeleted(DBHandle dbh, Table table) {
|
||||||
|
events.add(new DbEvent(DbNotifyType.TABLE_DELETED, table));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tableAdded(DBHandle dbh, Table table) {
|
||||||
|
events.add(new DbEvent(DbNotifyType.TABLE_ADDED, table));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,6 +101,13 @@ abstract class AbstractTransactionManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force transaction lock and terminate current transaction.
|
||||||
|
* @param rollback true if rollback of non-commited changes should occurs, false if commit
|
||||||
|
* should be done. NOTE: it can be potentially detrimental to commit an incomplete transaction
|
||||||
|
* and should be avoided.
|
||||||
|
* @param reason very short reason for requesting lock
|
||||||
|
*/
|
||||||
final void forceLock(boolean rollback, String reason) {
|
final void forceLock(boolean rollback, String reason) {
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
|
@ -119,6 +126,13 @@ abstract class AbstractTransactionManager {
|
||||||
terminateTransaction(rollback, true);
|
terminateTransaction(rollback, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminate current transaction.
|
||||||
|
* @param rollback true if rollback of non-commited changes should occurs, false if commit
|
||||||
|
* should be done. NOTE: it can be potentially detrimental to commit an incomplete transaction
|
||||||
|
* and should be avoided.
|
||||||
|
* @param notify true for listeners to be notified else false
|
||||||
|
*/
|
||||||
abstract void terminateTransaction(boolean rollback, boolean notify);
|
abstract void terminateTransaction(boolean rollback, boolean notify);
|
||||||
|
|
||||||
final synchronized void unlock() {
|
final synchronized void unlock() {
|
||||||
|
|
|
@ -284,8 +284,10 @@ public interface DomainObject {
|
||||||
public boolean lock(String reason);
|
public boolean lock(String reason);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels any previous lock and acquires it.
|
* Force transaction lock and terminate current transaction.
|
||||||
* @param rollback if true, any changes in made with the previous lock should be discarded.
|
* @param rollback true if rollback of non-commited changes should occurs, false if commit
|
||||||
|
* should be done. NOTE: it can be potentially detrimental to commit an incomplete transaction
|
||||||
|
* which should be avoided.
|
||||||
* @param reason very short reason for requesting lock
|
* @param reason very short reason for requesting lock
|
||||||
*/
|
*/
|
||||||
public void forceLock(boolean rollback, String reason);
|
public void forceLock(boolean rollback, String reason);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue