Package com.sun.jini.outrigger

Source Code of com.sun.jini.outrigger.EntryHandle

/*
* 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;
    }
}
TOP

Related Classes of com.sun.jini.outrigger.EntryHandle

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.