Package com.sun.sgs.impl.kernel

Source Code of com.sun.sgs.impl.kernel.LockingAccessCoordinator$TxnListener

/*
* Copyright 2007-2010 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
* --
*/

package com.sun.sgs.impl.kernel;

import com.sun.sgs.app.TransactionAbortedException;
import com.sun.sgs.app.TransactionConflictException;
import com.sun.sgs.app.TransactionTimeoutException;
import com.sun.sgs.impl.profile.ProfileCollectorHandle;
import com.sun.sgs.impl.service.transaction.TransactionCoordinator;
import com.sun.sgs.impl.service.transaction.TransactionCoordinatorImpl;
import com.sun.sgs.impl.sharedutil.LoggerWrapper;
import static com.sun.sgs.impl.sharedutil.Objects.checkNull;
import com.sun.sgs.impl.sharedutil.PropertiesWrapper;
import com.sun.sgs.impl.util.lock.LockConflict;
import com.sun.sgs.impl.util.lock.LockConflictType;
import com.sun.sgs.impl.util.lock.LockManager;
import com.sun.sgs.impl.util.lock.LockRequest;
import com.sun.sgs.impl.util.lock.TxnLockManager;
import com.sun.sgs.impl.util.lock.TxnLocker;
import com.sun.sgs.kernel.AccessCoordinator;
import com.sun.sgs.kernel.AccessReporter;
import com.sun.sgs.kernel.AccessReporter.AccessType;
import com.sun.sgs.kernel.AccessedObject;
import com.sun.sgs.profile.AccessedObjectsDetail;
import com.sun.sgs.profile.AccessedObjectsDetail.ConflictType;
import com.sun.sgs.service.Transaction;
import com.sun.sgs.service.TransactionInterruptedException;
import com.sun.sgs.service.TransactionListener;
import com.sun.sgs.service.TransactionProxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import static java.util.logging.Level.CONFIG;
import static java.util.logging.Level.FINER;
import java.util.logging.Logger;

/**
* An implementation of {@link AccessCoordinator} that uses locking to handle
* conflicts. <p>
*
* This implementation checks for deadlock whenever an access request is
* blocked due to a conflict.  It selects the youngest transaction as the
* deadlock victim, determining the age using the originally requested start
* time for the task associated with the transaction.  The implementation does
* not deny requests that would not result in deadlock.  When requests block,
* it services the requests in the order that they arrive. <p>
*
* The methods that this class provides to implement {@code AccessReporter} are
* not thread safe, and should either be called from a single thread or else
* protected with external synchronization. <p>
*
* The {@link #LockingAccessCoordinator constructor} supports the following
* configuration properties: <p>
*
* <dl style="margin-left: 1em">
*
* <dt> <i>Property:</i> <b>{@value #LOCK_TIMEOUT_PROPERTY}</b> <br>
<i>Default:</i> {@value #DEFAULT_LOCK_TIMEOUT_PROPORTION} times the
*  value of the {@code com.sun.sgs.txn.timeout} property, if specified,
*  otherwise times the value of the default transaction timeout.
*
* <dd style="padding-top: .5em">The maximum number of milliseconds to wait for
*  obtaining a lock.  The value must be greater than {@code 0}, and should
*  be less than the transaction timeout. <p>
*
* <dt> <i>Property:</i> <b>{@value #NUM_KEY_MAPS_PROPERTY}</b> <br>
<i>Default:</i> {@value #NUM_KEY_MAPS_DEFAULT}
*
* <dd style="padding-top: .5em">The number of maps to use for associating keys
*  and maps.  The number of maps controls the amount of concurrency, and
*  should typically be set to a value to support concurrent access by the
*  number of active threads.  The value must be greater than {@code
*  0}. <p>
*
* </dl> <p>
*
* This class uses the {@link Logger} named {@code
* com.sun.sgs.impl.kernel.LockingAccessCoordinator} to log information at the
* following logging levels: <p>
*
* <ul>
* <li> {@link Level#CONFIG CONFIG} - Creating an instance
* <li> {@link Level#FINER FINER} - Beginning and ending transactions
* </ul>
*/
public class LockingAccessCoordinator extends AbstractAccessCoordinator {

    /** The class name. */
    private static final String CLASS =
  "com.sun.sgs.impl.kernel.LockingAccessCoordinator";

    /**
     * The property for specifying the maximum number of milliseconds to wait
     * for obtaining a lock.
     */
    public static final String LOCK_TIMEOUT_PROPERTY =
  CLASS + ".lock.timeout";

    /**
     * The proportion of the transaction timeout to use for the lock timeout if
     * no lock timeout is specified.
     */
    public static final double DEFAULT_LOCK_TIMEOUT_PROPORTION = 0.1;

    /**
     * The property for specifying the number of maps to use for associating
     * keys and maps.  The number of maps controls the amount of concurrency.
     */
    public static final String NUM_KEY_MAPS_PROPERTY =
  CLASS + ".num.key.maps";

    /** The default number of key maps. */
    public static final int NUM_KEY_MAPS_DEFAULT = 8;

    /** The logger for this class. */
    static final LoggerWrapper logger = new LoggerWrapper(
  Logger.getLogger(LockingAccessCoordinator.class.getName()));

    /** Maps transactions to lockers. */
    private final ConcurrentMap<Transaction, LockerImpl> txnMap =
  new ConcurrentHashMap<Transaction, LockerImpl>();

    /** The lock manager. */
    private final TxnLockManager<Key> lockManager;

    /* -- Public constructor -- */

    /**
     * Creates an instance of this class.
     *
     * @param  properties the configuration properties
     * @param  txnProxy the transaction proxy
     * @param  profileCollectorHandle the profile collector handle
     * @throws  IllegalArgumentException if the values of the configuration
     *    properties are illegal
     */
    public LockingAccessCoordinator(
  Properties properties,
  TransactionProxy txnProxy,
  ProfileCollectorHandle profileCollectorHandle)
    {
  super(txnProxy, profileCollectorHandle);
  PropertiesWrapper wrappedProps = new PropertiesWrapper(properties);
  long txnTimeout = wrappedProps.getLongProperty(
      TransactionCoordinator.TXN_TIMEOUT_PROPERTY,
      TransactionCoordinatorImpl.BOUNDED_TIMEOUT_DEFAULT);
  long defaultLockTimeout = Math.max(
      1L, (long) (txnTimeout * DEFAULT_LOCK_TIMEOUT_PROPORTION));
  long lockTimeout = wrappedProps.getLongProperty(
      LOCK_TIMEOUT_PROPERTY, defaultLockTimeout, 1, Long.MAX_VALUE);
  int numKeyMaps = wrappedProps.getIntProperty(
      NUM_KEY_MAPS_PROPERTY, NUM_KEY_MAPS_DEFAULT, 1, Integer.MAX_VALUE);
  lockManager = new TxnLockManager<Key>(lockTimeout, numKeyMaps);
  if (logger.isLoggable(CONFIG)) {
      logger.log(CONFIG,
           "Created LockingAccessCoordinator with properties:" +
           "\n  txn timeout: " + txnTimeout +
           "\n  lock timeout: " + lockTimeout +
           "\n  num key maps: " + numKeyMaps);
  }
    }

    /* -- Implement AccessCoordinator -- */

    /** {@inheritDoc} */
    public <T> AccessReporter<T> registerAccessSource(
  String sourceName, Class<T> objectIdType)
    {
  checkNull("objectIdType", objectIdType);
  return new AccessReporterImpl<T>(sourceName);
    }

    /**
     * {@inheritDoc} <p>
     *
     * This implementation does not record information about completed
     * transactions, so it always returns {@code null}.
     */
    public Transaction getConflictingTransaction(Transaction txn) {
  checkNull("txn", txn);
  return null;
    }

    /* -- Implement AccessCoordinatorHandle -- */

    /** {@inheritDoc} */
    public void notifyNewTransaction(
  Transaction txn, long requestedStartTime, int tryCount)
    {
  if (tryCount < 1) {
      throw new IllegalArgumentException(
    "The tryCount must not be less than 1");
  }
  LockerImpl locker =
      new LockerImpl(lockManager, txn, requestedStartTime);
  LockerImpl existing = txnMap.putIfAbsent(txn, locker);
  if (existing != null) {
      throw new IllegalStateException("Transaction already started");
  }
  if (logger.isLoggable(FINER)) {
      logger.log(FINER, "begin {0}, requestedStartTime:{1,number,#}",
           locker, requestedStartTime);
  }
  txn.registerListener(new TxnListener(txn));
    }

    /* -- Other methods -- */

    /**
     * Returns the locker associated with a transaction.
     *
     * @param  txn the transaction
     * @return  the locker
     * @throws  IllegalArgumentException if the transaction is not active
     */
    LockerImpl getLocker(Transaction txn) {
  checkNull("txn", txn);
  LockerImpl locker = txnMap.get(txn);
  if (locker == null) {
      throw new IllegalArgumentException(
    "Transaction not active: " + txn);
  }
  return locker;
    }

    /**
     * Releases the locks for the transaction and reports object accesses to
     * the profiling system.
     *
     * @param  txn the finished transaction
     */
    private void endTransaction(Transaction txn) {
  LockerImpl locker = getLocker(txn);
  logger.log(FINER, "end {0}", locker);
  locker.releaseAll();
  txnMap.remove(txn);
  profileCollectorHandle.setAccessedObjectsDetail(locker);
    }

    /* -- Other classes -- */

    /**
     * Define a locker that records information about the transaction
     * requesting locks, and descriptions.
     */
    public static class LockerImpl extends TxnLocker<Key>
  implements AccessedObjectsDetail
    {
  /** The lock requests made by this transaction. */
  private final List<AccessedObjectImpl> requests =
      new ArrayList<AccessedObjectImpl>();

  /** A map from keys to descriptions, or {@code null}. */
  private Map<Key, Object> keyToDescriptionMap = null;

  /**
   * Whether the transaction has ended.  Used when assertions are enabled
   * to check the thread safety of accesses to the requests field.
   * Synchronize on the requests field, rather than the locker object
   * itself, when accessing this field, to avoid lock ordering problems.
   */
  private boolean ended = false;

  /**
   * Creates an instance of this class.
   *
   * @param  lockManager the lock manager for this locker
   * @param  txn the associated transaction
   * @param  requestedStartTime the time milliseconds that the task
   *    associated with the transaction was originally
   *    requested to start
   * @throws  IllegalArgumentException if {@code requestedStartTime}
   *    is less than {@code 0}
   */
  LockerImpl(TxnLockManager<Key> lockManager,
       Transaction txn,
       long requestedStartTime)
  {
      super(lockManager, txn, requestedStartTime);
  }

  /**
   * {@inheritDoc} <p>
   *
   * This implementation records the new request and uses a local
   * class.
   */
  @Override
  protected LockRequest<Key> newLockRequest(
      Key key, boolean forWrite, boolean upgrade)
  {
      assert !getEnded();
      AccessedObjectImpl request =
    new AccessedObjectImpl(this, key, forWrite, upgrade);
      requests.add(request);
      return request;
  }

  /** Release all locks. */
  void releaseAll() {
      assert setEnded();
      LockManager<Key> lockManager = getLockManager();
      for (LockRequest<Key> request : requests) {
    lockManager.releaseLock(this, request.getKey());
      }
  }

  /**
   * Returns a string representation of this object.  This implementation
   * prints the associated transaction, for debugging.
   *
   * @return  a string representation of this object
   */
  @Override
  public String toString() {
      return txn.toString();
  }

  /* -- Implement AccessedObjectsDetail -- */

  /** {@inheritDoc} */
  public List<AccessedObject> getAccessedObjects() {
      return Collections.<AccessedObject>unmodifiableList(requests);
  }

  /** {@inheritDoc} */
  public ConflictType getConflictType() {
      LockConflict<Key> conflict = getConflict();
      if (conflict == null) {
    return ConflictType.NONE;
      } else if (conflict.getType() == LockConflictType.DEADLOCK) {
    return ConflictType.DEADLOCK;
      } else {
    return ConflictType.ACCESS_NOT_GRANTED;
      }
  }

  /** {@inheritDoc} */
  public byte[] getConflictingId() {
      LockConflict<Key> conflict = getConflict();
      if (conflict != null) {
    LockerImpl conflictingLocker =
        (LockerImpl) conflict.getConflictingLocker();
        return conflictingLocker.getTransaction().getId();
      } else {
    return null;
      }
  }

  /* -- Other methods -- */

  /**
   * Sets the description associated with a key for this locker.  The
   * description should not be {@code null}.  Does not replace an
   * existing description.
   *
   * @param  key the key
   * @param  description the description
   */
  void setDescription(Key key, Object description) {
      assert key != null;
      assert description != null;
      if (keyToDescriptionMap == null) {
    keyToDescriptionMap = new HashMap<Key, Object>();
      }
      if (!keyToDescriptionMap.containsKey(key)) {
    keyToDescriptionMap.put(key, description);
      }
  }

  /**
   * Gets the description associated with a key for this locker.
   *
   * @param  key the key
   * @return  the description or {@code null}
   */
  Object getDescription(Key key) {
      return (keyToDescriptionMap == null)
    ? null : keyToDescriptionMap.get(key);
  }

  /**
   * Sets the specified conflict if this locker does not have a conflict
   * set.
   */
  synchronized void setConflictIfNeeded(LockConflict<Key> conflict) {
      if (getConflict() == null) {
    setConflict(conflict);
      }
  }

  /**
   * Marks the transaction as ended.
   *
   * @return  whether the transaction is newly ended
   */
  private boolean setEnded() {
      synchronized (requests) {
    if (ended) {
        return false;
    } else {
        ended = true;
        return true;
    }
      }
  }

  /** Returns whether the transaction has ended. */
  private boolean getEnded() {
      synchronized (requests) {
    return ended;
      }
  }
    }

    /** Implement {@code AccessedObject}. */
    private static class AccessedObjectImpl extends LockRequest<Key>
  implements AccessedObject
    {
  /**
   * Creates an instance of this class.
   *
   * @param  locker the locker that requested the lock
   * @param  key the key identifying the lock
   * @param  forWrite whether a write lock was requested
   * @param  upgrade whether an upgrade was requested
   */
  AccessedObjectImpl(LockerImpl locker,
         Key key,
         boolean forWrite,
         boolean upgrade)
  {
      super(locker, key, forWrite, upgrade);
  }

  /* -- Implement AccessedObject -- */

  /** {@inheritDoc} */
  public String getSource() {
      return getKey().source;
  }

  /** {@inheritDoc} */
  public Object getObjectId() {
      return getKey().objectId;
  }

  /** {@inheritDoc} */
  public AccessType getAccessType() {
      return getForWrite() ? AccessType.WRITE : AccessType.READ;
  }

  /** {@inheritDoc} */
  public Object getDescription() {
      return ((LockerImpl) getLocker()).getDescription(getKey());
  }

  /**
   * Two instances are equal if they are instances of this class, and
   * have the same source, object ID, and access type.
   *
   * @param  object the object to compare with
   * @return  whether this instance equals the argument
   */
  @Override
  public boolean equals(Object object) {
      if (object == this) {
    return true;
      } else if (object instanceof AccessedObjectImpl) {
    AccessedObjectImpl other = (AccessedObjectImpl) object;
    return getKey().equals(other.getKey()) &&
        getForWrite() == other.getForWrite();
      } else {
    return false;
      }
  }

  @Override
  public int hashCode() {
      return getKey().hashCode() ^ (getForWrite() ? 1 : 0);
  }

  /* -- Other methods -- */

  /** Print fields, for debugging. */
  @Override
  public String toString() {
      return "AccessedObjectImpl[" + getLocker() + ", " +
    getKey() + ", " +
    (getForWrite() ? "WRITE" : getUpgrade() ? "UPGRADE" : "READ") +
    "]";
  }
    }

    /** Represents an object as identified by a source and an object ID. */
    private static final class Key {

  /** The source. */
  final String source;

  /** The object ID. */
  final Object objectId;

  /**
   * Creates an instance of this class.
   *
   * @param  source the source of the object
   * @param  objectId the object ID of the object
   */
  Key(String source, Object objectId) {
      checkNull("source", source);
      checkNull("objectId", objectId);
      this.source = source;
      this.objectId = objectId;
  }

  /* -- Compare source and object ID -- */

  @Override
  public boolean equals(Object object) {
      if (object == this) {
    return true;
      } else if (object instanceof Key) {
    Key key = (Key) object;
    return source.equals(key.source) &&
        objectId.equals(key.objectId);
      } else {
    return false;
      }
  }

  @Override
  public int hashCode() {
      return source.hashCode() ^ objectId.hashCode();
  }

  /** Print fields, for debugging. */
  @Override
  public String toString() {
      return source + ":" + objectId;
  }
    }

    /** Implement {@link AccessReporter}. */
    private class AccessReporterImpl<T> extends AbstractAccessReporter<T> {

  /**
   * Creates an instance of this class.
   *
   * @param  source the source of the objects managed by this
   *    reporter
   */
  AccessReporterImpl(String source) {
      super(source);
  }

  /* -- Implement AccessReporter -- */

  /** {@inheritDoc} */
  public void reportObjectAccess(
      Transaction txn, T objectId, AccessType type, Object description)
  {
      checkNull("type", type);
      LockerImpl locker = getLocker(txn);
      Key key = new Key(source, objectId);
      if (description != null) {
    locker.setDescription(key, description);
      }
      LockConflict<Key> conflict =
    lockManager.lock(locker, key, type == AccessType.WRITE);
      if (conflict != null) {
    locker.setConflictIfNeeded(conflict);
    String descriptionMsg = "";
    if (description != null) {
        try {
      descriptionMsg = ", description:" + description;
        } catch (RuntimeException e) {
        }
    }
    String accessMsg = "Access txn:" + txn +
        ", type:" + type +
        ", source:" + source +
        ", objectId:" + objectId +
        descriptionMsg +
        " failed: ";
    LockerImpl conflictingLocker =
        (LockerImpl) conflict.getConflictingLocker();
    String conflictMsg = ", with conflicting transaction " +
        conflictingLocker.getTransaction();
    TransactionAbortedException exception;
    switch (conflict.getType()) {
    case TIMEOUT:
        exception = new TransactionTimeoutException(
      accessMsg + "Transaction timed out" + conflictMsg);
        break;
    case DENIED:
        exception = new TransactionConflictException(
      accessMsg + "Access denied" + conflictMsg);
        break;
    case INTERRUPTED:
        exception = new TransactionInterruptedException(
      accessMsg + "Transaction interrupted" + conflictMsg);
        break;
    case DEADLOCK:
        exception = new TransactionConflictException(
      accessMsg + "Transaction deadlock" + conflictMsg);
        break;
    default:
        throw new AssertionError(
      "Should not be " + conflict.getType());
    }
    txn.abort(exception);
    throw exception;
      }
  }

  /** {@inheritDoc} */
  public void setObjectDescription(
      Transaction txn, T objectId, Object description)
  {
      LockerImpl locker = getLocker(txn);
      if (description == null) {
    checkNull("objectId", objectId);
      } else {
    locker.setDescription(new Key(source, objectId), description);
      }
  }
    }

    /**
     * A transaction listener that calls {@link #endTransaction} when called
     * after the transaction completes.  Use a listener instead of a
     * transaction participant to make sure that locks are released only after
     * all of the transaction participants have finished their work.
     */
    private class TxnListener implements TransactionListener {

  /** The transaction. */
  private final Transaction txn;

  /**
   * Creates an instance of this class.
   *
   * @param  txn the transaction we're listening for
   */
  TxnListener(Transaction txn) {
      this.txn = txn;
  }

  /**
   * {@inheritDoc} <p>
   *
   * This implementation does nothing.
   */
  public void beforeCompletion() { }

  /**
   * {@inheritDoc} <p>
   *
   * This implementation calls {@link #endTransaction}.
   */
  public void afterCompletion(boolean committed) {
      endTransaction(txn);
  }

        /** {@inheritDoc} */
        public String getTypeName() {
            return TxnListener.class.getName();
        }
    }
}
TOP

Related Classes of com.sun.sgs.impl.kernel.LockingAccessCoordinator$TxnListener

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.