/*
* 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 net.jini.io.MarshalledInstance;
import com.sun.jini.landlord.LeasedResource;
/**
* This object holds an annotated reference to an
* <code>EntryRep</code> object. Currently there is one annotation,
* which is a hash code for the object that can be used as a
* quick-reject comparison when scanning through the list. The handle
* holds a hash code that is based on the bytes that encode the first
* <i>N</i> fields, where <i>N</i> is the number of fields in the
* entry up to a maximum (currently this maximum is 16 (64 bits
* divided by 4 bits/field), so that 4 is the minimum number of bits
* per field in the hash).
* <p>
* When comparing, the template's own hash is calculated, and also a
* mask that masks out the hash codes of wildcard fields. A template
* will match an entry only if the entry's EntryHandle hash masked with
* the template's wildcard mask is the same as the template's hash.
* <p>
* Care must be taken since the template may be a supertype of the type
* being searched. This is why the number of fields in the static
* methods is passed as an argument, not simply taken from the entry in
* question. When a template's hash is being created, its hash value
* is calculated as if it were of the class being searched, with the
* subclass's field count. Any extra fields are assumed to be
* wildcards. This means that the template's hash must be recalculated
* for each subclass it is compared against, but this only happens once
* per known subclass, and so is probably not onerous. <p>
*
* There is a particular risk with the removal of entries outside of
* transactions. Ideally marking an entry as removed and making the
* removal durable would be atomic with respect to other
* operations. But this would require holding a lock across disk I/O
* which we try to avoid. In particular it would hold up the progress
* of searches that match the entry in question, even though the very
* next entry might be a suitable match. One alternative would be to
* make the removal durable, then while holding the entry's lock mark
* the entry as removed, but this would allow competing takes to both
* get the same entry (this could be corrected by making the 2nd take
* lose when it goes back to try and complete the removal, and then
* continues its query, but since logging happens up in
* OutriggerServerImpl restarting the query would be inconvenient, it
* would probably also result in a number of unnecessary log
* records). We could mark the entry as removed, release the entry's
* lock, and then make the removal durable. However, this allows for
* the possibility of a 2nd query that matches the entry coming in
* after the entry has been removed, but before the removal has been
* made durable, finding no matches and returning null, and then the
* server crashing before the removal is made durable. When the server
* came back up the entry would be available again, and if the 2nd
* query was repeated it could then return the entry that had been
* marked as removed. Effectively an entry would have disappeared and
* then reappeared. <p>
*
* Our solution is to introduce the <i>removePending</i> flag. When an
* entry is to be removed outside of a transaction the removePending
* flag is set by calling <code>provisionallyRemove</code>, the
* removal is made durable, the entry is removed internally and the
* removePending flag cleared (generally by calling
* <code>remove</code> on the appropriate <code>EntryHolder</code> or
* on the <code>EntryHolderSet</code> - either will remove the entry
* from all the internal tables and clear the removePending flag). <p>
*
* Any operation that will definitively indicate that a given entry
* has been removed must not only check to see if the entry has been
* removed but also that removePending is not set (the
* <code>isProvisionallyRemoved</code> method returns the state of the
* removePending flag). If removePending is set the operation must
* either block until removePending is cleared (this can be
* accomplished using the <code>waitOnCompleteRemoval</code> method),
* indicating that the removal has been made durable, or return in
* such a way that the entry's state is left ambiguous. Note, because
* any I/O failure while logging will result in the space crashing a
* set removePending flag will only transition to cleared after a
* removal has been made durable, thus an operation blocked on the
* removePending flag should never need to go back and see if the
* entry has become available. <p>
*
* Note some of the method of this class are synchronized internally,
* while other are synchronized externally. Methods which need to be
* synchronized externally are called out in their comments.
*
* @author Sun Microsystems, Inc.
*/
// We do not store this data on the EntryRep object itself because it
// is not really part of the client<->JavaSpaces service protocol --
// some implementations of EntryHolder may not choose to use this
// mechanism. It does add an extra object per EntryRep object in
// those that *do* use it, and so we may want to re-examine this in the
// future.
class EntryHandle extends BaseHandle implements LeaseDesc, Transactable {
/** the content hash for the rep */
private long hash;
/**
* If this entry is locked by one or more transaction the info
* on those transactions, otherwise <code>null</code>.
*/
private TxnState txnState;
/**
* <code>true</code> if this entry has to been seen as removed,
* but the removal has not yet been committed to disk
*/
private boolean removePending = false;
/**
* Create a new handle, calculating the hash for the object.
* If <code>mgr</code> is non-<code>null</code> start the entry
* as write locked under the given transaction.
* @param rep The rep of the entry this is a handle for
* @param mgr If this entry is being written under a transaction the
* manager for that transaction, otherwise <code>null</code>
* @param holder If mgr is non-<code>null</code> this must be
* the holder holding this handle. Otherwise it may be
* <code>null</code>
*/
EntryHandle(EntryRep rep, TransactableMgr mgr, EntryHolder holder) {
super(rep);
hash = (rep != null ? hashFor(rep, rep.numFields()) : -1);
if (mgr == null) {
txnState = null;
} else {
if (holder == null)
throw new NullPointerException("EntryHandle:If mgr is " +
"non-null holder must be non-null");
txnState = new TxnState(mgr, TransactableMgr.WRITE, holder);
}
}
// inherit doc comment
public LeasedResource getLeasedResource() {
return rep();
}
/**
* Return this handle's content hash.
*/
long hash() {
return hash;
}
/**
* Calculate the hash for a particular entry, assuming the given number
* of fields.
*
* @see #hashFor(EntryRep,int,EntryHandleHashDesc)
*/
static long hashFor(EntryRep rep, int numFields) {
return hashFor(rep, numFields, null);
}
/**
* Calculate the hash for a particular entry, assuming the given
* number of fields, filling in the fields of <code>desc</code>
* with the relevant values. <code>desc</code> may be
* <code>null</code>. <code>numFields</code> must be >= the number
* of fields in the <code>rep</code> object (this is not
* checked).
*
* @see #hashFor(EntryRep,int)
* @see #descFor(EntryRep,int)
* @see EntryHandleHashDesc
*/
private static long
hashFor(EntryRep rep, int numFields, EntryHandleHashDesc hashDesc)
{
if (rep == null || numFields == 0)
return 0;
int bitsPerField = Math.max(64 / numFields, 4); // at least 4 bits
int fieldsInHash = 64 / bitsPerField; // max fields used
long mask = // per-field bit mask
0xffffffffffffffffL >>> (64 - bitsPerField);
long hash = 0; // current hash value
// field counts will be different if rep is a template of a superclass
long endField = Math.min(fieldsInHash, rep.numFields());
// set the appropriate rep of the overall hash for the field's hash
for (int i = 0; i < endField; i++)
hash |= (hashForField(rep, i) & mask) << (i * bitsPerField);
// If someone wants to remember these results, fill 'em in
if (hashDesc != null) {
hashDesc.bitsPerField = bitsPerField;
hashDesc.fieldsInHash = fieldsInHash;
hashDesc.mask = mask;
}
return hash;
}
/**
* Return the template description -- mask and hash.
*
* @see EntryHandleTmplDesc
*/
static EntryHandleTmplDesc descFor(EntryRep tmpl, int numFields) {
EntryHandleHashDesc hashDesc = new EntryHandleHashDesc();
EntryHandleTmplDesc tmplDesc = new EntryHandleTmplDesc();
// Get the hash and the related useful information
tmplDesc.hash = hashFor(tmpl, numFields, hashDesc);
// Create the mask to mask away wildcard fields
for (int i = 0; i < hashDesc.fieldsInHash; i++) {
// If this field is one we have a value for, set bits in the mask
if (i < tmpl.numFields() && tmpl.value(i) != null)
tmplDesc.mask |= (hashDesc.mask << (i * hashDesc.bitsPerField));
}
// Ensure that the non-value fields are masked out
tmplDesc.hash &= tmplDesc.mask;
return tmplDesc;
}
/**
* Return the hash value for a given field, which is then merged in
* as part of the overall hash for the entry. The last 32 bytes of
* the field value are used (or fewer if there are fewer).
*
* @see #hashFor(EntryRep,int,EntryHandleHashDesc)
*/
static long hashForField(EntryRep rep, int field) {
MarshalledInstance v = rep.value(field);
if (v == null) // for templates, it's just zero
return 0;
else
return v.hashCode();
}
public String toString() {
return "0x" + Long.toHexString(hash) + " [" + rep() + "]";
}
/**
* Return <code>true</code> if the operation <code>op</code> under
* the given transaction (represented by the transaction's manager)
* can be performed on the object represented by this handle. The
* thread calling this method should own this object's lock.
*/
// $$$ Calling this method when we don't own the lock on this
// object seems a bit dicey, but that is exactly what we do in
// EntryHolder.SimpleRepEnum.nextRep(). Working it through
// it seems to work in that particular case, but it seems fragile.
boolean canPerform(TransactableMgr mgr, int op) {
if (txnState == null)
return true; // all operations are legal on a non-transacted entry
return txnState.canPerform(mgr, op);
}
/**
* Return <code>true</code> if the given transaction is already
* known to the entry this handle represents. The
* thread calling this method should own this object's lock.
*/
boolean knownMgr(TransactableMgr mgr) {
if (txnState == null)
return (mgr == null); // The only mgr we know about is the null mgr
return txnState.knownMgr(mgr);
}
/**
* Return <code>true</code> if we are being managed the given
* manager is the only one we know about. The thread calling this
* method should own this object's lock.
*/
boolean onlyMgr(TransactableMgr mgr) {
if (txnState == null)
return false;
return txnState.onlyMgr(mgr);
}
/**
* Return <code>true</code> if the entry this handle represents is
* being managed within any transaction. The thread calling this
* method should own this object's lock.
*/
boolean managed() {
return txnState != null;
}
/**
* Add into the collection any transactions that are known to this
* handle. The thread calling this method should own this object's
* lock.
*/
void addTxns(java.util.Collection collection) {
if (txnState == null)
return; // nothing to add
txnState.addTxns(collection);
}
/**
* Add <code>mgr</code> to the list of known managers, setting the
* the type of lock on this entry to <code>op</code>. The thread
* calling this method should own this object's lock. Assumes
* that <code>op</code> is compatible with any lock currently
* associated with this entry. <code>holder</code> is the the
* <code>EntryHolder</code> holding this handle.
*/
void add(TransactableMgr mgr, int op, EntryHolder holder) {
if (txnState == null) {
txnState = new TxnState(mgr, op, holder);
} else {
txnState.add(mgr, op);
}
}
/**
* It this entry is read locked promote to take locked and return
* true, otherwise return false. Assumes that the object is
* locked and the take is being performed under the one
* transaction that owns a lock on the entry.
*/
boolean promoteToTakeIfNeeded() {
return txnState.promoteToTakeIfNeeded();
}
/**
* Returns <code>true</code> it this entry has been removed
* outside of a transaction, but that removal has not yet been
* committed to disk.The thread calling this method should own this
* object's lock.
*/
boolean isProvisionallyRemoved() {
assert Thread.holdsLock(this);
return removePending;
}
/**
* Marks this entry as being removed outside of a transaction but
* not yet committed to disk. The thread calling this method should
* own this object's lock.
*/
void provisionallyRemove() {
assert Thread.holdsLock(this);
assert !removePending;
removePending = true;
}
/**
* Called after the removal of a provisionally removed entry has
* been committed to disk and the handle has been removed from its
* holder. The thread calling this method should own this object's
* lock.
*/
void removalComplete() {
assert Thread.holdsLock(this);
if (removePending) {
removePending = false;
notifyAll();
}
}
/**
* If this entry has been marked for removal by a
* non-transactional operation, but that operation has not be
* yet been committed to disk, block until the operation has been
* committed to disk, otherwise return immediately. The
* thread calling this method should own this object's lock.
*/
void waitOnCompleteRemoval() throws InterruptedException {
assert Thread.holdsLock(this);
while (removePending) {
wait();
}
}
/**************************************************
* Methods required by the Transactable interface
**************************************************/
public synchronized int prepare(TransactableMgr mgr,
OutriggerServerImpl space)
{
if (txnState == null)
throw new IllegalStateException("Can't prepare an entry not " +
"involved in a transaction");
final int rslt = txnState.prepare(mgr, space, this);
if (txnState.empty())
txnState = null;
return rslt;
}
public synchronized void abort(TransactableMgr mgr,
OutriggerServerImpl space)
{
if (txnState == null)
throw new IllegalStateException("Can't abort an entry not " +
"involved in a transaction");
final boolean last = txnState.abort(mgr, space, this);
if (last)
txnState = null;
}
public synchronized void commit(TransactableMgr mgr,
OutriggerServerImpl space)
{
if (txnState == null)
throw new IllegalStateException("Can't commit an entry not " +
"involved in a transaction");
final boolean last = txnState.commit(mgr, space, this);
if (last)
txnState = null;
}
}