/*
* The contents of this file are subject to the Open Software License
* Version 3.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.rosenlaw.com/OSL3.0.htm
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* This file is an original work developed by Netymon Pty Ltd
* (http://www.netymon.com, mailto:mail@netymon.com). Portions created
* by Netymon Pty Ltd are Copyright (c) 2006 Netymon Pty Ltd.
* All Rights Reserved.
*
* Contributor(s):
* Refactoring to focus on write-lock management contributed by Netymon
* Pty Ltd on behalf of Topaz Foundation under contract.
*/
package org.mulgara.resolver;
// Java2 packages
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
// Third party packages
import org.apache.log4j.Logger;
// Local packages
import org.mulgara.query.MulgaraTransactionException;
import org.mulgara.util.StackTrace;
/**
* Manages the Write-Lock.
*
* Manages tracking the ownership of the write-lock.
* Provides a facility to trigger a heuristic rollback of any transactions still
* valid on session close.
* Maintains the write-queue
*
* @created 2006-10-06
*
* @author <a href="mailto:andrae@netymon.com">Andrae Muys</a>
*
* @version $Revision: $
*
* @modified $Date: $
*
* @maintenanceAuthor $Author: $
*
* @company <A href="mailto:mail@netymon.com">Netymon Pty Ltd</A>
*
* @copyright ©2006 <a href="http://www.netymon.com/">Netymon Pty Ltd</a>
*
* @licence Open Software License v3.0</a>
*/
public class MulgaraTransactionManager {
/** Logger. */
private static final Logger logger =
Logger.getLogger(MulgaraTransactionManager.class.getName());
// Write lock is associated with a session.
private DatabaseSession sessionHoldingWriteLock;
// Used to support write-lock reservation.
private DatabaseSession sessionReservingWriteLock;
// Used to synchronize access to other fields.
private final ReentrantLock mutex;
private final Condition writeLockCondition;
public MulgaraTransactionManager() {
this.sessionHoldingWriteLock = null;
this.sessionReservingWriteLock = null;
this.mutex = new ReentrantLock();
this.writeLockCondition = this.mutex.newCondition();
}
/**
* Obtains the write lock.
*/
void obtainWriteLock(DatabaseSession session) throws MulgaraTransactionException {
acquireMutex();
try {
if (sessionHoldingWriteLock == session) {
return;
}
while (writeLockHeld() || (writeLockReserved() && !writeLockReserved(session))) {
try {
writeLockCondition.await();
} catch (InterruptedException ei) {
throw new MulgaraTransactionException("Interrupted while waiting for write lock", ei);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Obtaining write lock\n" + new StackTrace());
}
sessionHoldingWriteLock = session;
} finally {
releaseMutex();
}
}
boolean isHoldingWriteLock(DatabaseSession session) {
acquireMutex();
try {
return sessionHoldingWriteLock == session;
} finally {
releaseMutex();
}
}
void releaseWriteLock(DatabaseSession session) throws MulgaraTransactionException {
acquireMutex();
try {
if (sessionHoldingWriteLock == null) {
return;
}
if (sessionHoldingWriteLock != session) {
throw new MulgaraTransactionException("Attempted to release write lock being held by another session");
}
if (logger.isDebugEnabled()) {
logger.debug("Releasing writelock\n" + new StackTrace());
}
sessionHoldingWriteLock = null;
writeLockCondition.signal();
} finally {
releaseMutex();
}
}
/**
* Used to replace the built in monitor to allow it to be properly released
* during potentially blocking operations. All potentially blocking
* operations involve writes, so in these cases the write-lock is reserved
* allowing the mutex to be safely released and then reobtained after the
* blocking operation concludes.
*/
private void acquireMutex() {
mutex.lock();
}
/**
* Used to reserve the write lock during a commit or rollback.
* Should only be used by a transaction manager.
*/
void reserveWriteLock(DatabaseSession session) throws MulgaraTransactionException {
acquireMutex();
try {
if (session != sessionReservingWriteLock && session != sessionHoldingWriteLock) {
throw new IllegalStateException("Attempt to reserve writelock without holding writelock");
}
if (session != sessionReservingWriteLock && sessionReservingWriteLock != null) {
throw new IllegalStateException("Attempt to reserve writelock when writelock already reserved");
}
sessionReservingWriteLock = session;
} finally {
releaseMutex();
}
}
boolean writeLockReserved() {
acquireMutex();
try {
return sessionReservingWriteLock != null;
} finally {
releaseMutex();
}
}
boolean writeLockReserved(DatabaseSession session) {
acquireMutex();
try {
return session == sessionReservingWriteLock;
} finally {
releaseMutex();
}
}
void releaseReserve(DatabaseSession session) {
acquireMutex();
try {
if (!writeLockReserved()) {
return;
}
if (!writeLockReserved(session)) {
throw new IllegalStateException("Attempt to release reserve without holding reserve");
}
sessionReservingWriteLock = null;
writeLockCondition.signal();
} finally {
releaseMutex();
}
}
private boolean writeLockHeld() {
return sessionHoldingWriteLock != null;
}
private void releaseMutex() {
if (!mutex.isHeldByCurrentThread()) {
throw new IllegalStateException("Attempt to release mutex without holding mutex");
}
mutex.unlock();
}
public void closingSession(DatabaseSession session) throws MulgaraTransactionException {
// This code should not be required, but is there to ensure the manager is
// reset regardless of errors in the factories.
acquireMutex();
try {
Throwable error = null;
if (writeLockReserved(session)) {
try {
releaseReserve(session);
} catch (Throwable th) {
logger.error("Error releasing reserve on force-close", th);
error = (error == null) ? th : error;
}
}
if (isHoldingWriteLock(session)) {
try {
releaseWriteLock(session);
} catch (Throwable th) {
logger.error("Error releasing write-lock on force-close", th);
error = (error == null) ? th : error;
}
}
if (error != null) {
if (error instanceof MulgaraTransactionException) {
throw (MulgaraTransactionException)error;
} else {
throw new MulgaraTransactionException("Error force releasing write-lock", error);
}
}
} finally {
releaseMutex();
}
}
}