/*
* Copyright 2013 Gordon Burgett and individual contributors
*
* 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 org.xflatdb.xflat.db;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicLong;
import org.xflatdb.xflat.transaction.TransactionManager;
/**
* The Base Class of all Transaction Managers.<p/>
* This class defines the contract required of Transaction Manager implementations
* by XFlat. XFlat users do not need to concern themselves with these methods, only
* the exposed TransactionManager interface.
* @author Gordon
*/
public abstract class EngineTransactionManager implements TransactionManager, AutoCloseable {
/**
* Gets a new commit ID for a transactionless write operation.
* All transactionless writes can be thought of as transactions that are
* automatically committed. This allows us to provide snapshot isolation between
* transactions and transactionless writes.
* @return A transaction ID that functions as a "snapshot ID" for this particular
* transactionless operation.
*/
public abstract long transactionlessCommitId();
/**
* Gets the ID of the earliest open transaction.
* @return The ID of the earliest open transaction.
*/
public abstract long getLowestOpenTransaction();
/**
* Called by an engine in order to bind itself to a transaction. This means
* that the engine has transactional data for this transaction, so the
* transaction manager will not forget about the transaction so long as
* engine remains bound to it.
* <p/>
* If the engine is already bound to the transaction, this method does nothing.
* @param engine The engine to bind to a transaction.
*/
public abstract void bindEngineToCurrentTransaction(EngineBase engine);
/**
* Unbinds the engine from all its bound and closed transactions except the given collection.<br/>
* The engine will not be unbound from any open transactions.
* @param engine The engine to unbind.
* @param transactionIds The transactions to remain bound to.
*/
public abstract void unbindEngineExceptFrom(EngineBase engine, Collection<Long> transactionIds);
/**
* Checks to see if the given transaction ID has been committed. If so,
* returns the transaction's commit ID. Otherwise returns -1.
* <p/>
* This method is only valid so long as at least one engine is bound
* to the transaction. If no engines are bound to the transaction,
* this may return erroneous data.
* @param transactionId The ID of the transaction to check.
* @return the transaction's commit ID if committed, -1 otherwise.
*/
public abstract long isTransactionCommitted(long transactionId);
/**
* Checks to see if the given transaction ID is not yet finished committing.
* <p/>
* If true, then the transaction has a commit ID assigned and some rows may
* be marked with that commit ID, but {@link org.xflatdb.xflat.transaction.Transaction#isCommitted() }
* will be false.
* @param transactionId The ID of the transaction to check.
* @return true if the transaction has begun committing but is not yet finished.
*/
public abstract boolean isCommitInProgress(long transactionId);
/**
* Checks to see if the given transaction ID has been reverted. If so,
* returns true, otherwise false.
* </p>
* This method is only valid so long as at least one engine is bound
* to the transaction. If no engines are bound to the transaction,
* this may return erroneous data.
* @param transactionId The ID of the transaction to check.
* @return true if the transaction is reverted, false otherwise.
*/
public abstract boolean isTransactionReverted(long transactionId);
private AtomicLong lastId = new AtomicLong();
/**
* Generates a new Transaction ID. The ID is composed of the lower
* 48 bits of {@link System#currentTimeMillis() } plus a 16-bit uniquifier.
* unfortunately this means we have a y6k problem :P I'll let my descendants
* deal with it. (seriously, I calculated it and we will run out of IDs on
* 10/17/6429 at around 3am).
* @return A new ID for a transaction.
*/
protected long generateNewId(){
long id;
long last;
do{
//bitshifting current time millis still gets us at least to the year 10,000 before it overflows.
id = System.currentTimeMillis() << 16;
last = lastId.get();
long lastTs = (last & 0xFFFFFFFFFFFF0000l);
if(lastTs >= id){
//the last timestamp was at the same millisecond as our new ID, need to use uniquifier.
//last timestamp could also be after our new ID, this is because System.currentTimeMillis() is not absolutely
//synchronized across all threads.
int u = (int)(last & 0xFFFFl) + 1;
if(u > 0xFFFF){
try {
//we can't roll over, need to slow down rate of transaction generation.
Thread.sleep(1);
} catch (InterruptedException ex) {
//don't care
}
//try again, hopefully currentTimeMillis rolled over.
last = -1; //clear it out so that we fail the compare and set
continue;
}
id = lastTs | u;
}
}while(!lastId.compareAndSet(last, id));
return id;
}
/**
* Returns true if any transactions are currently open.
*/
public abstract boolean anyOpenTransactions();
/**
* Attempts to recover from an unexpected shutdown if necessary. The
* transaction manager ought to use journaling so that it can revert partially-
* committed transactions here.
* @param db The database that initiated the recovery. It is used to spin
* up the tables that any partially-committed transactions were bound to.
*/
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();
}