org.apache.jena.tdb.transaction.Transaction Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jena-tdb Show documentation
Show all versions of jena-tdb Show documentation
TDB is a storage subsystem for Jena and ARQ, it is a native triple store providing persistent storage of triples/quads.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.jena.tdb.transaction;
import java.io.IOException ;
import java.util.ArrayList ;
import java.util.Iterator ;
import java.util.List ;
import org.apache.jena.atlas.logging.Log ;
import org.apache.jena.query.ReadWrite ;
import org.apache.jena.tdb.store.DatasetGraphTDB ;
import org.apache.jena.tdb.sys.FileRef ;
import org.apache.jena.tdb.sys.SystemTDB ;
/** A transaction. Much of the work is done in the transaction manager */
public class Transaction
{
private final long id ;
private final String label ;
private final TransactionManager txnMgr ;
private final Journal journal ;
private final ReadWrite mode ;
private final List nodeTableTrans = new ArrayList<>() ;
private final List blkMgrs = new ArrayList<>() ;
// The dataset this is a transaction over - may be a commited, pending dataset.
private final DatasetGraphTDB basedsg ;
private final long version ;
private final List> iterators ; // Tracking iterators
private DatasetGraphTxn activedsg ;
private TxnState state ;
// How this transaction ended.
enum TxnOutcome { UNFINISHED, W_ABORTED, W_COMMITED, R_CLOSED, R_ABORTED, R_COMMITED }
private TxnOutcome outcome ;
private boolean changesPending ;
public Transaction(DatasetGraphTDB dsg, long version, ReadWrite mode, long id, String label, TransactionManager txnMgr) {
this.id = id ;
if (label == null )
label = "Txn" ;
label = label+"["+id+"]" ;
switch(mode) {
case READ : label = label+"/R" ; break ;
case WRITE : label = label+"/W" ; break ;
}
this.label = label ;
this.txnMgr = txnMgr ;
this.basedsg = dsg ;
this.version = version ;
this.mode = mode ;
this.journal = ( txnMgr == null ) ? null : txnMgr.getJournal() ;
activedsg = null ; // Don't know yet.
this.iterators = null ; //new ArrayList<>() ; // Debugging aid.
state = TxnState.ACTIVE ;
outcome = TxnOutcome.UNFINISHED ;
changesPending = (mode == ReadWrite.WRITE) ;
}
/*
* Commit is a 4 step process:
*
* 1/ commitPrepare - call all the components to tell them we are going to
* commit.
*
* 2/ Actually commit - write the commit point to the journal
*
* 3/ commitEnact -- make the changes to the original data
*
* 4/ commitClearup -- release resources The transaction manager is the
* place which knows all the components in a transaction.
*
* Synchronization note: The transaction manager can call back into a
* transaction so make sure that the lock for this object is released before
* calling into the transaction manager
*/
public void commit() {
synchronized (this) {
// Do prepare, write the COMMIT record.
// Enacting is left to the TransactionManager.
switch(mode) {
case READ:
outcome = TxnOutcome.R_COMMITED ;
break ;
case WRITE:
if ( state != TxnState.ACTIVE )
throw new TDBTransactionException("Transaction has already committed or aborted") ;
// ---- Prepare
try {
prepare() ;
} catch (RuntimeException ex)
{
if ( isIOException(ex) )
SystemTDB.errlog.warn("IOException during 'prepare' : attempting transaction abort: "+ex.getMessage()) ;
else
SystemTDB.errlog.warn("Exception during 'prepare' : attempting transaction abort", ex) ;
state = TxnState.ACTIVE ;
try {
abort() ;
} catch (RuntimeException ex2)
{
// It's a mess.
SystemTDB.errlog.warn("Exception during 'abort' after 'prepare'", ex2) ;
}
throw new TDBTransactionException("Abort during prepare - transaction did not commit", ex) ;
}
// ---- end prepare
try {
journal.write(JournalEntryType.Commit, FileRef.Journal, null) ;
journal.sync() ; // Commit point.
} catch (RuntimeException ex) {
// It either did all commit or didn't but we don't know which.
// Some low level system error - probably a sign of something
// serious like disk error.
if ( isIOException(ex) )
SystemTDB.errlog.warn("IOException during 'commit' : transaction status not known (but not a partial commit): "+ex.getMessage()) ;
else
SystemTDB.errlog.warn("Exception during 'commit' : transaction status not known (but not a partial commit): ",ex) ;
throw new TDBTransactionException("Exception at commit point", ex) ;
}
outcome = TxnOutcome.W_COMMITED ;
break ;
}
state = TxnState.COMMITED ;
// The transaction manager does the enact and clearup calls
}
try { txnMgr.notifyCommit(this) ; }
catch (RuntimeException ex) {
if ( isIOException(ex) )
SystemTDB.errlog.warn("IOException after commit point : transaction commited but internal status not recorded properly : "+ex.getMessage()) ;
else
SystemTDB.errlog.warn("Exception after commit point : transaction commited but internal status not recorded properly", ex) ;
throw new TDBTransactionException("Exception after commit point - transaction did commit", ex) ;
}
}
private boolean isIOException(Throwable ex) {
while (ex != null) {
if ( ex instanceof IOException )
return true ;
ex = ex.getCause() ;
}
return false ;
}
private void prepare() {
state = TxnState.PREPARING ;
for ( BlockMgrJournal x : blkMgrs )
x.commitPrepare(this) ;
for ( NodeTableTrans x : nodeTableTrans )
x.commitPrepare(this) ;
}
public void abort() {
synchronized (this) {
switch (mode) {
case READ :
state = TxnState.ABORTED ;
outcome = TxnOutcome.R_ABORTED ;
break ;
case WRITE :
if ( state != TxnState.ACTIVE )
throw new TDBTransactionException("Transaction has already committed or aborted") ;
try {
// Clearup.
for ( BlockMgrJournal x : blkMgrs )
x.abort(this) ;
for ( NodeTableTrans x : nodeTableTrans )
x.abort(this) ;
}
catch (RuntimeException ex) {
if ( isIOException(ex) )
SystemTDB.errlog.warn("IOException during 'abort' : " + ex.getMessage()) ;
else
SystemTDB.errlog.warn("Exception during 'abort'", ex) ;
// It's a bit of a mess!
throw new TDBTransactionException("Exception during abort - transaction did abort", ex) ;
}
state = TxnState.ABORTED ;
outcome = TxnOutcome.W_ABORTED ;
// [TxTDB:TODO]
// journal.truncate to last commit
// Not need currently as the journal is only written in
// prepare.
break ;
}
}
try { txnMgr.notifyAbort(this) ; }
catch (RuntimeException ex) {
if ( isIOException(ex) )
SystemTDB.errlog.warn("IOException during post-abort (transaction did abort): "+ex.getMessage()) ;
else
SystemTDB.errlog.warn("Exception during post-abort (transaction did abort)", ex) ;
// It's a bit of a mess!
throw new TDBTransactionException("Exception after abort point - transaction did abort", ex) ;
}
}
/** transaction close happens after commit/abort
* read transactions "auto commit" on close().
* write transactions must call abort or commit.
*/
public void close() {
//Log.info(this, "Peek = "+peekCount+" ; count = "+count) ;
synchronized (this) {
switch (state) {
case CLOSED :
return ; // Can call close() repeatedly.
case ACTIVE :
if ( mode == ReadWrite.READ ) {
commit() ;
outcome = TxnOutcome.R_CLOSED ;
} else {
SystemTDB.errlog.warn("Transaction not commited or aborted: " + this) ;
abort() ;
}
break ;
default :
}
state = TxnState.CLOSED ;
// Clear per-transaction temporary state.
if ( iterators != null )
iterators.clear() ;
}
// Called once.
txnMgr.notifyClose(this) ;
}
/** A write transaction has been processed and all chanages propagated back to the database */
/*package*/ void signalEnacted()
{
synchronized (this)
{
if ( ! changesPending )
Log.warn(this, "Transaction was a read transaction or a write transaction that has already been flushed") ;
changesPending = false ;
}
}
public ReadWrite getMode() { return mode ; }
public boolean isRead() { return mode == ReadWrite.READ ; }
public boolean isWrite() { return mode == ReadWrite.WRITE ; }
public TxnState getState() { return state ; }
public long getTxnId() { return id ; }
public TransactionManager getTxnMgr() { return txnMgr ; }
public DatasetGraphTxn getActiveDataset() { return activedsg ; }
public long getVersion() { return version ; }
/*package*/ void setActiveDataset(DatasetGraphTxn activedsg) {
this.activedsg = activedsg ;
if ( activedsg.getTransaction() != this )
Log.warn(this, "Active DSG does not point to this transaction; "+this) ;
}
/*package*/ Journal getJournal() { return journal ; }
private int count = 0 ;
private int peekCount = 0 ;
public void addIterator(Iterator > iter) {
count++ ;
peekCount = Math.max(peekCount, count) ;
if ( iterators != null )
iterators.add(iter) ;
}
// The code does not perfectly record end of iterator.
public void removeIterator(Iterator > iter) {
count-- ;
if ( iterators != null )
iterators.remove(iter) ;
if ( count == 0 ) {
peekCount = 0 ;
}
}
/** Return the list of items registered for the transaction lifecycle */
public List lifecycleComponents() {
List x = new ArrayList<>() ;
x.addAll(nodeTableTrans) ;
x.addAll(blkMgrs) ;
return x ;
}
/*package*/ void addComponent(NodeTableTrans ntt) {
nodeTableTrans.add(ntt) ;
}
/*package*/ void addComponent(BlockMgrJournal blkMgr) {
blkMgrs.add(blkMgr) ;
}
public DatasetGraphTDB getBaseDataset() {
return basedsg ;
}
@Override
public String toString() {
return "Transaction: " + id + " : Mode=" + mode + " : State=" + state + " : " + basedsg.getLocation().getDirectoryPath() ;
}
public String getLabel() {
return label ;
}
}