/*
* 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 com.sun.jini.outrigger;
import java.util.HashSet;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.core.transaction.server.TransactionConstants;
import net.jini.space.InternalSpaceException;
/**
* Class that manages transaction related state on behalf of
* <code>EntryHandle</code>s. Can accommodate entries locked by
* more than one transaction. The synchronization of this object is
* managed by the <code>EntryHandle</code> that owns it.
*
* @author Sun Microsystems, Inc.
*
* @see EntryHandle
*/
class TxnState {
/**
* The list of known managers. In order to keep things small in the
* common case that there is only one known manager, <code>mgrs</code>
* is managed as a ``list'' in one of two states -- it is either a
* direct reference to the only manager for this handle, or a reference
* to an <code>HashSet</code> with entries for each associated manager.
*/
private Object mgrs;
/**
* The current state of the handle, such as <code>READ</code> or
* <code>TAKE</code>.
*/
private int state;
/**
* The holder the handle which owns this object is in
*/
final private EntryHolder holder;
/** Logger for logging information about entry matching */
private static final Logger matchingLogger =
Logger.getLogger(OutriggerServerImpl.matchingLoggerName);
/** Logger for logging transaction related information */
private static final Logger txnLogger =
Logger.getLogger(OutriggerServerImpl.txnLoggerName);
/**
* Create a new <code>TxnState</code>. It will start initially
* with the type of lock indicated by <code>state</code> under the
* transaction managed by <code>mgr</code>. <code>holder</code> is
* the holder the associated entry handle is in.
*/
TxnState(TransactableMgr mgr, int state, EntryHolder holder) {
this.mgrs = mgr;
this.state = state;
this.holder = holder;
txnLogger.log(Level.FINER, "TxnState: TxnState: state = {0}",
TransactableMgr.stateNames[state]);
}
/**
* Prepare to commit this object's part of the transaction. Return
* the prepare's status.
*/
int prepare(TransactableMgr mgr, OutriggerServerImpl space,
EntryHandle owner)
{
txnLogger.log(Level.FINEST, "TxnState: prepare: state = {0}",
TransactableMgr.stateNames[state]);
if (state == TransactableMgr.READ) {
final int locksLeft = removeMgr(mgr);
/* If there is more than one lock left resolving this lock
* does not change anything (takes still can't take the
* entry and reads could already) Also need to make sure
* it has not be removed (say by expiration or a cancel)
* since this would cause to send events for entries that
* were removed before it became visible. Also could cause
* ifExists queries to hang on to the entry and block
* indefinitely. If they had already send the EntryTransition
* for the removal.
*/
if (locksLeft <= 1 && !owner.removed()) {
// Has the potential to resolve conflicts/generate events
if (locksLeft == 1) {
space.recordTransition(
new EntryTransition(owner, mgr(), true, false, false));
} else if (locksLeft == 0) {
space.recordTransition(
new EntryTransition(owner, null, true, false, false));
} else {
throw new AssertionError("Fewer than 0 locks left");
}
}
return TransactionConstants.NOTCHANGED;
}
return TransactionConstants.PREPARED;
}
/**
* Abort this object's part of the transaction. Return true
* if this clears the last transaction associated with this object.
*/
boolean abort(TransactableMgr mgr, OutriggerServerImpl space,
EntryHandle owner)
{
boolean rslt = true;
txnLogger.log(Level.FINEST, "TxnState: abort: state = {0}",
TransactableMgr.stateNames[state]);
if (state == TransactableMgr.READ || state == TransactableMgr.TAKE) {
final int locksLeft = removeMgr(mgr);
rslt = (locksLeft == 0);
/* If there is more than one lock left resolving this
* lock does not change anything (takes still can't
* take the entry and reads could already). Also
* need to make sure it has not be removed (say
* by expiration or a cancel), otherwise
* we could log a transition to visible after
* the removal was logged, this could cause
* ifExists queries to hang on to the entry
* and block indefinitely. (This raises an
* issue if remote events were sent for re-appearance)
*/
if (locksLeft <= 1 && !owner.removed()) {
// Has the potential to resolve conflicts/generate events
if (locksLeft == 1) {
/* There could have only been multiple locks if
* they were all read locks, thus the lock the
* abort resolves must h be a read lock, and the
* remaining lock must be a read lock too.
* Therefore this must be an availability but not a
* visibility transition.
*/
assert state == TransactableMgr.READ;
space.recordTransition(
new EntryTransition(owner, mgr(), true, false, false));
} else if (locksLeft == 0) {
// Only a visibility transition if the lock being
// dropped was a take lock
final boolean visibility = (state == TransactableMgr.TAKE);
space.recordTransition(
new EntryTransition(owner, null, true, visibility,
false));
} else {
throw new AssertionError("Fewer than 0 locks left");
}
}
} else {
/* must be a write, make the entry disappear, will
* call recordTransition()
*/
holder.remove(owner, false);
}
return rslt;
}
/**
* Commit this object's part of the transaction. The
* <code>space</code> is the <code>OutriggerServerImpl</code> on
* which the operation happens -- some commit operations have
* space-wide side effects (for example, a commit of a
* <code>write</code> operation can cause event notifications for
* clients registered under the transaction's parent). Return true
* if this clears the last transaction associated with this
* object.
*/
boolean commit(TransactableMgr mgr, OutriggerServerImpl space,
EntryHandle owner)
{
txnLogger.log(Level.FINEST, "TxnState: commit: state = {0}",
TransactableMgr.stateNames[state]);
switch (state) {
case TransactableMgr.WRITE:
//!! assumption -- single level transactions:
//!! we pass null where we should pass the parent txn
if (owner.removed())
// if removed() then this entry must
// have been taken as well as written under this
// transaction.
return true;
space.recordTransition(
new EntryTransition(owner, null, true, true, true));
return (removeMgr(mgr) == 0);
case TransactableMgr.READ:
// Read locked entries should never get prepared.
throw new InternalSpaceException
("committing a read locked entry");
case TransactableMgr.TAKE:
// remove calls recordTransition()
holder.remove(owner, false);
// Resolves a lock
return true; // Take-locked entries only have one Txn
default:
throw new InternalSpaceException("unexpected state in "
+ "TxnState.commit(): " + state);
}
}
/**
* Remove the given mgr from the list of known managers. Return the
* number of mgrs still associated with this entry.
*/
private int removeMgr(TransactableMgr mgr) {
if (mgrs == null)
return 0;
if (mgr == mgrs) {
mgrs = null;
return 0;
}
final HashSet tab = (HashSet) mgrs;
tab.remove(mgr);
return tab.size();
}
/**
* Add <code>mgr</code> to the list of known managers, setting the
* state of this handle to <code>op</code>.
*/
void add(TransactableMgr mgr, int op) {
if (mgr == mgrs)
return; // already the only one known
if (mgrs instanceof TransactableMgr) {
Object origMgr = mgrs;
mgrs = new HashSet(7);
((HashSet) mgrs).add(origMgr);
}
// if mgr is in already this is harmless, and checking to prevent
// a redundant addition is more expensive
((HashSet) mgrs).add(mgr);
monitor(mgr);
state = op;
}
/**
* If we need to, add this manager to the list of transactions that
* need to be monitored because of conflicts over this entry. Any
* existing blocking txn is sufficient.
*
* @see TxnMonitorTask#addSibling
*/
private void monitor(TransactableMgr mgr) {
Txn txn = (Txn) mgr;
Iterator it = ((HashSet) mgrs).iterator();
while (it.hasNext()) {
Txn otherTxn = (Txn) it.next();
if (otherTxn != mgr && otherTxn.monitorTask() != null) {
otherTxn.monitorTask().addSibling(txn);
return;
}
}
}
/**
* It this entry is read locked promote to take locked and return
* true, otherwise return false. Assumes the take is being
* performed under the one transaction that owns a lock on the
* entry.
*/
boolean promoteToTakeIfNeeded() {
// We can assume our state is ether WRITE or READ, if it was
// take this method would not be called (since take blocks
// other takes in the same transaction canPerform() would have
// returned false)
if (state == TransactableMgr.WRITE)
return false;
state = TransactableMgr.TAKE;
return true;
}
/**
* Returns <code>true</code> if the operation <code>op</code>
* under the transaction manager by <code>mgr</code> is legal on
* the associated entry given the operations already performed on
* the entry under other transactions. It is legal to:
* <ul>
* <li>Read an entry that has been read in any other transaction,
* or that has been written under the same transaction (that is,
* if <code>mgr</code> is the same as the writing transaction).
* <li>Take an entry that was written under the same transaction, or
* which was read <em>only</em> under the same transaction (that is,
* no other active transactions have also read it).
* </ul>
* All other operations are not legal, so <code>canPerform</code>
* otherwise returns <code>false</code>.
*/
boolean canPerform(TransactableMgr mgr, int op) {
//Note: If two transactions are performed
// within the same TransactableMgr
// (see Txn.java), that means its in
// the same Transaction (see Transaction.java).
// Operations under the same Transaction scope
// are the same as if under a null Transaction
// i.e. all operations are visible.
//
// Semantics need to be added for multi-level
// transactions.
if (matchingLogger.isLoggable(Level.FINER)) {
matchingLogger.log(Level.FINER,
"TxnState: canPerform({0}, {1}): state = {2}",
new Object[]{mgr, new Integer(op),
TransactableMgr.stateNames[state]});
}
switch (state) {
case TransactableMgr.READ:
if (op == TransactableMgr.READ)
return true;
return onlyMgr(mgr); // can only modify if I'm the only participant
case TransactableMgr.WRITE:
if ( (op == TransactableMgr.READ) ||
(op == TransactableMgr.TAKE) )
return onlyMgr(mgr);
return false; // can't read unless I'm the writer
case TransactableMgr.TAKE:
return false; // can't take anything taken any time
}
return false;
}
/**
* Return <code>true</code> if <code>mgr</code> is one of the managers
* known to be managing this entry.
*/
boolean knownMgr(TransactableMgr mgr) {
if (mgr == null)
return false;
if (mgr == mgrs)
return true;
if (mgrs instanceof HashSet)
return ((HashSet) mgrs).contains(mgr);
return false;
}
/**
* Return <code>true</code> if the given manager is the only one
* we know about.
*/
boolean onlyMgr(TransactableMgr mgr) {
if (mgr == null)
return false;
if (mgr == mgrs)
return true;
if (mgrs instanceof HashSet) {
HashSet tab = (HashSet) mgrs;
return (tab.size() == 1 && tab.contains(mgr));
}
return false;
}
/**
* Add all the managers of this transaction to the given
* collection.
*/
void addTxns(java.util.Collection collection) {
if (mgrs == null)
return;
if (mgrs instanceof HashSet) {
final HashSet tab = (HashSet)mgrs;
collection.addAll((HashSet) mgrs);
return;
}
collection.add(mgrs);
}
/**
* Return true if there are no more transactions associated with
* this object.
*/
boolean empty() {
if (mgrs == null)
return true;
if (mgrs instanceof HashSet)
return ((HashSet)mgrs).isEmpty();
return false;
}
/** Used by mgr() */
final private TransactableMgr[] rslt = new TransactableMgr[1];
/**
* Returns the one manager associated with this transaction.
* Throws an AssertionError if there is more or fewer than one
* manager associated with this transaction.
*/
private TransactableMgr mgr() {
if (mgrs == null)
throw new AssertionError
("mgr() called on a TxnState with no manager");
if (mgrs instanceof HashSet) {
final HashSet tab = (HashSet)mgrs;
if (tab.size() != 1)
throw new AssertionError
("mgr() called on TxnState with more than one manager");
tab.toArray(rslt);
return rslt[0];
}
return (TransactableMgr)mgrs;
}
}