Permalink
Browse files

Nearly finished with testing Transactions, I need to test a couple er…

…ror scenarios related to transaction durability.

Signed-off-by: gburgett <gordon.burgett@gmail.com>
  • Loading branch information...
gburgett committed Jan 24, 2013
1 parent f3a3613 commit d9ad002ba97e270f1d2b0dd2973ccd8c5a7e968b
@@ -363,6 +363,7 @@ protected void setId(Element row, String id){
row.setAttribute("id", id, XFlatDatabase.xFlatNs);
}
+ //<editor-fold desc="transactions">
/**
* Checks whether this engine has any transactional updates in an uncommitted
@@ -372,6 +373,29 @@ protected void setId(Element row, String id){
*/
protected abstract boolean hasUncomittedData();
+ /**
+ * Called when a transaction is committed to write the committed data to disk.
+ * After this method returns, the data should be stored in non-volatile storage.
+ * @param tx
+ */
+ public void commit(Transaction tx){
+
+ }
+
+ /**
+ * Called when a transaction is committed to revert the given transaction ID.
+ * This may be called even if a transaction was previously committed in this engine,
+ * because it was not fully committed across all engines.
+ * @param tx
+ * @param isRecovering true if this transaction is being reverted during recovery
+ * at startup.
+ */
+ public void revert(long tx, boolean isRecovering){
+
+ }
+
+ //</editor-fold>
+
/**
* Represents one row in the database. The row contains a set of
* {@link RowData} which represents the committed and uncommitted data in
@@ -12,7 +12,7 @@
*
* @author Gordon
*/
-public abstract class EngineTransactionManager implements TransactionManager {
+public abstract class EngineTransactionManager implements TransactionManager, AutoCloseable {
/**
* Gets a new commit ID for a transactionless write operation.
@@ -121,5 +121,23 @@ protected long generateNewId(){
return id;
}
+
+ /**
+ * Returns true if any transactions are currently open.
+ * @return
+ */
+ public abstract boolean anyOpenTransactions();
+
+ /**
+ * Attempts to recover from an unexpected shutdown if necessary.
+ * @param db
+ */
+ public abstract void recover(XFlatDatabase db);
+ /**
+ * Closes any resources in use by this transaction manager in preparation
+ * for shutdown. Any exceptions at this point should be logged but not
+ * rethrown since this is only used during shutdown.
+ */
+ public abstract void close();
}
@@ -50,6 +50,11 @@ private IdAccessor(Class<T> pojoType, PropertyDescriptor idProperty, Field idFie
* @return The accessor, which may have already been created and cached.
*/
public static <U> IdAccessor<U> forClass(Class<U> pojoType){
+ if(pojoType.isPrimitive() ||
+ String.class.equals(pojoType)){
+ return null;
+ }
+
IdAccessor<U> ret = (IdAccessor<U>)cachedAccessors.get(pojoType);
if(ret != null){
return ret;
@@ -123,7 +123,7 @@ private EngineBase getEngine(Interval<T> interval){
File file = new File(directory, name + ".xml");
this.knownShards.put(interval, file);
- metadata = this.getMetadataFactory().makeTableMetadata(name, file);
+ metadata = this.getMetadataFactory().makeTableMetadata(this.getTableName(), file);
metadata.config = TableConfig.Default; //not even really used for our purposes
TableMetadata weWereLate = openShards.putIfAbsent(interval, metadata);
@@ -154,21 +154,12 @@ private EngineBase getEngine(Interval<T> interval){
throw new XflatException("Attempt to read or write to an engine in an uninitialized state");
}
- //all operations are writes for the purposes of the sharded engine.
- //NOT TRUE! Need to fix this!
- ensureWriteReady();
try{
-
- try{
- return action.act(getEngine(range));
- }
- catch(EngineStateException ex){
- //try one more time with a potentially new engine, if we still fail then let it go
- return action.act(getEngine(range));
- }
-
- }finally{
- writeComplete();
+ return action.act(getEngine(range));
+ }
+ catch(EngineStateException ex){
+ //try one more time with a potentially new engine, if we still fail then let it go
+ return action.act(getEngine(range));
}
}
@@ -213,6 +204,19 @@ else if(state == EngineState.Running){
return false;
}
+ @Override
+ public void revert(long txId, boolean isRecovering){
+ if(!isRecovering){
+ //the individual shard engines will also have been registered.
+ return;
+ }
+
+ //we will need to revert over all known shards in order to recover.
+ for(Interval<T> interval : this.knownShards.keySet()){
+ this.getEngine(interval).revert(txId, isRecovering);
+ }
+ }
+
@Override
protected boolean spinUp() {
if(!this.state.compareAndSet(EngineState.Uninitialized, EngineState.SpinningUp)){
@@ -51,7 +51,7 @@ public String getName(){
public boolean canSpinDown(){
EngineBase engine = this.engine.get();
- return lastActivity + 3000 < System.currentTimeMillis() && engine == null || !engine.hasUncomittedData();
+ return lastActivity + config.getInactivityShutdownMs() < System.currentTimeMillis() && engine == null || !engine.hasUncomittedData();
}
public EngineBase getEngine(){
@@ -133,20 +133,23 @@ private TableMetadata makeNewTableMetadata(String name, File engineFile, TableCo
Class<? extends IdGenerator> generatorClass = config.getIdGenerator();
if(generatorClass != null){
ret.idGenerator = makeIdGenerator(generatorClass);
- if(!ret.idGenerator.supports(idType)){
+ if(idType != null && !ret.idGenerator.supports(idType)){
throw new XflatException("Id Generator " + generatorClass.getName() +
" does not support type " + idType);
}
}
else {
+
//pick using our strategy
- for(Class<? extends IdGenerator> g : dbConfig.getIdGeneratorStrategy()){
- IdGenerator gen = makeIdGenerator(g);
- if(gen.supports(idType)){
- ret.idGenerator = gen;
- break;
+ if(idType != null)
+ for(Class<? extends IdGenerator> g : dbConfig.getIdGeneratorStrategy()){
+ IdGenerator gen = makeIdGenerator(g);
+ if(gen.supports(idType)){
+ ret.idGenerator = gen;
+ break;
+ }
}
- }
+
if(ret.idGenerator == null){
throw new XflatException("Could not pick id generator for type " + idType);
}
@@ -185,7 +188,8 @@ private TableMetadata makeTableMetadataFromDocument(String name, File engineFile
}
}
ret.idGenerator = makeIdGenerator(generatorClass);
- if(!ret.idGenerator.supports(idType)){
+
+ if(idType != null && !ret.idGenerator.supports(idType)){
throw new XflatException("Id Generator " + generatorClass + " does not support " +
" ID type " + idType);
}
@@ -34,6 +34,7 @@
import org.gburgett.xflat.convert.converters.StringConverters;
import org.gburgett.xflat.transaction.ThreadContextTransactionManager;
import org.gburgett.xflat.transaction.TransactionManager;
+import org.gburgett.xflat.util.DocumentFileWrapper;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
@@ -203,14 +204,17 @@ public void Initialize(){
this.executorService = new ScheduledThreadPoolExecutor(this.config.getThreadCount());
if(this.transactionManager == null){
- this.transactionManager = new ThreadContextTransactionManager();
+ this.transactionManager = new ThreadContextTransactionManager(new DocumentFileWrapper(new File(directory, "xflat_transaction")));
}
this.InitializeScheduledTasks();
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
+ //recover transactional state if necessary
+ this.transactionManager.recover(this);
+
}catch(Exception ex){
this.state.set(DatabaseState.Uninitialized);
throw new XflatException("Initialization error", ex);
@@ -224,10 +228,14 @@ public void Initialize(){
*/
public void shutdown(){
try{
- this.shutdown(0);
+ this.doShutdown(0);
}catch(TimeoutException ex){
throw new RuntimeException("A timeout occured that should never have happened", ex);
}
+ finally{
+ //close all resources
+ this.getEngineTransactionManager().close();
+ }
}
/**
@@ -239,6 +247,16 @@ public void shutdown(){
* the timeout expired.
*/
public void shutdown(int timeout) throws TimeoutException{
+ try{
+ this.doShutdown(timeout);
+ }
+ finally{
+ //close all resources
+ this.getEngineTransactionManager().close();
+ }
+ }
+
+ private void doShutdown(int timeout) throws TimeoutException{
if(!this.state.compareAndSet(DatabaseState.Running, DatabaseState.ShuttingDown)){
return;
}
@@ -398,6 +416,18 @@ public void extendConversionService(PojoConverter extender){
@Override
public <T> Table<T> getTable(Class<T> type, String name){
+
+ TableMetadata table = getMetadata(type, name);
+
+ TableBase ret = table.getTable(type);
+ return (Table<T>)ret;
+ }
+
+ public EngineBase getEngine(String name){
+ return getMetadata(null, name).provideEngine();
+ }
+
+ private TableMetadata getMetadata(Class<?> type, String name){
if(this.state.get() == DatabaseState.Uninitialized){
throw new IllegalStateException("Database has not been initialized");
}
@@ -408,34 +438,38 @@ public void extendConversionService(PojoConverter extender){
if(name == null || name.startsWith("xflat_")){
throw new IllegalArgumentException("Table name cannot be null or start with 'xflat_': " + name);
}
-
- if(!this.getConversionService().canConvert(type, Element.class) ||
- !this.getConversionService().canConvert(Element.class, type)){
-
- try {
- //try to load the pojo converter
- loadPojoConverter();
-
- } catch (Exception ex) {
- throw new UnsupportedOperationException("No conversion available between " +
- type + " and " + Element.class, ex);
- }
-
- //check again
+ if(type != null){
if(!this.getConversionService().canConvert(type, Element.class) ||
- !this.getConversionService().canConvert(Element.class, type)){
- throw new UnsupportedOperationException("No conversion available between " +
- type + " and " + Element.class);
+ !this.getConversionService().canConvert(Element.class, type)){
+
+ try {
+ //try to load the pojo converter
+ loadPojoConverter();
+
+ } catch (Exception ex) {
+ throw new UnsupportedOperationException("No conversion available between " +
+ type + " and " + Element.class, ex);
+ }
+
+ //check again
+ if(!this.getConversionService().canConvert(type, Element.class) ||
+ !this.getConversionService().canConvert(Element.class, type)){
+ throw new UnsupportedOperationException("No conversion available between " +
+ type + " and " + Element.class);
+ }
}
}
+
//see if we have a cached engine already
TableMetadata table = this.tables.get(name);
if(table == null){
TableConfig tblConfig = this.tableConfigs.get(name);
Class<?> idType = String.class;
- IdAccessor accessor = IdAccessor.forClass(type);
- if(accessor.hasId()){
- idType = accessor.getIdType();
+ if(type != null){
+ IdAccessor accessor = IdAccessor.forClass(type);
+ if(accessor != null && accessor.hasId()){
+ idType = accessor.getIdType();
+ }
}
table = this.metadataFactory.makeTableMetadata(name, new File(getDirectory(), name + ".xml"), tblConfig, idType);
@@ -447,8 +481,7 @@ public void extendConversionService(PojoConverter extender){
}
}
- TableBase ret = table.getTable(type);
- return (Table<T>)ret;
+ return table;
}
private AtomicBoolean pojoConverterLoaded = new AtomicBoolean(false);
Oops, something went wrong.

0 comments on commit d9ad002

Please sign in to comment.