/* GASH 2 DBLock.java The GANYMEDE object storage system. Created: 2 July 1996 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2014 The University of Texas at Austin Ganymede is a registered trademark of The University of Texas at Austin Contact information Web site: http://www.arlut.utexas.edu/gash2 Author Email: ganymede_author@arlut.utexas.edu Email mailing list: ganymede@arlut.utexas.edu US Mail: Computer Science Division Applied Research Laboratories The University of Texas at Austin PO Box 8029, Austin TX 78713-8029 Telephone: (512) 835-3200 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 arlut.csd.ganymede.server; import java.util.List; /*------------------------------------------------------------------------------ abstract class DBLock ------------------------------------------------------------------------------*/ /** * <p>DBLocks arbitrate access to {@link * arlut.csd.ganymede.server.DBObjectBase DBObjectBase} objects in the * Ganymede server's {@link arlut.csd.ganymede.server.DBStore * DBStore}.</p> * * <p>Threads wishing to commit updates to object bases in the DBStore * must be in possession of an exclusive established DBWriteLock.</p> * * <p>Threads wishing to be able to read multiple objects in a * transaction-consistent matter should have an established DBReadLock * or DBDumpLock, depending on the priority needs of the thread. * Typically, DBDumpLocks are used when the server is in a hurry to * dump out build data or to update its on-disk ganymede.db file.</p> * * <p>The general scheme is that any number of readers and/or dumpers * can read from an object base simultaneously. Once a DBWriteLock * calls {@link * arlut.csd.ganymede.server.DBLock#establish(java.lang.Object)} on an * object base, all active readers are allowed to complete their * reading, but no new read lock may be established until the writer * has a chance to get in and make its update and then signals * completion by calling release(). Writers are given priority in the * DBLock queue over readers.</p> * * <p>Similarly, if there are a number of writer locks queued up for * update access to a DBObjectBase in the DBStore when a thread * attempts to establish a DBDumpLock, those writers are allowed to * complete their updates, but no new writer is queued until the dump * thread finishes dumping the locked bases.</p> * * <p>All of this priority logic is implemented in the establish() * methods of the concrete DBLock subclasses.</p> * * <p>As mentioned above, all DBLock's are issued in the context of * one or more {@link arlut.csd.ganymede.server.DBObjectBase * DBObjectBase} objects. The DBObjectBases are actually the things * being locked. To maintain multi-threaded safety of the lock system * across multiple DBObjectBases, the DBLock {@link * arlut.csd.ganymede.server.DBLock#establish(java.lang.Object) * establish()} and {@link arlut.csd.ganymede.server.DBLock#release() * release()} methods (as implemented in {@link * arlut.csd.ganymede.server.DBReadLock DBReadLock}, {@link * arlut.csd.ganymede.server.DBWriteLock DBWriteLock}, and {@link * arlut.csd.ganymede.server.DBDumpLock DBDumpLock}) are all * synchronized on the Ganymede server's {@link * arlut.csd.ganymede.server.DBLockSync DBLockSync} singleton. This * synchronization is critical for the proper functioning of the * DBLock system.</p> * * <p>There is currently no intrinsic support for handling timeouts * within the DBLock class hierarchy, and locks can persist * indefinitely.</p> * * <p>However, the {@link arlut.csd.ganymede.server.GanymedeSession * GanymedeSession} will be notified by RMI via the {@link * arlut.csd.ganymede.server.GanymedeSession#unreferenced()} method if * a remote client dies and by the scheduled {@link * arlut.csd.ganymede.server.timeOutTask} in conjunction with the * server's {@link * arlut.csd.ganymede.server.GanymedeServer#clearIdleSessions()} * method if the client goes idle for too long.</p> * * <p>In either case, the client's GanymedeSession abort will force * all locks held by the client to {@link * arlut.csd.ganymede.server.DBLock#abort()}, thus releasing the * locks.</p> * * <p>The possessors of DBLocks are identified by a key Object that is * provided on the call to {@link * arlut.csd.ganymede.server.DBLock#establish(java.lang.Object)}. A * given key may only have one DBWriteLock established at a time, but * it may have multiple concurrent DBReadLocks and DBDumpLocks * established if there are no conflicting DBWriteLocks in effect.</p> */ public abstract class DBLock { /** * All DBLock's establish() and release() methods synchronize their * critical sections on a singleton DBLockSync object held in the * Ganymede server's DBStore object in order to guarantee that all * lock negotiations are thread-safe. */ final DBLockSync lockSync; /* -- */ DBLock(DBLockSync sync) { this.lockSync = sync; } /** * Returns true if the lock has the given {@link * arlut.csd.ganymede.server.DBObjectBase DBObjectBase} locked. */ boolean isLocked(DBObjectBase candidateBase) { synchronized (lockSync) { if (!isLocked()) { return false; } for (DBObjectBase base: getBases()) { if (base == candidateBase) { return true; } } } return false; } /** * Returns true if the lock has all of the {@link * arlut.csd.ganymede.server.DBObjectBase DBObjectBase} objects in * the provided List locked. */ boolean isLocked(List<DBObjectBase> bases) { synchronized (lockSync) { if (!isLocked()) { return false; } return arlut.csd.Util.VectorUtils.difference(bases, getBases()).size() == 0; } } /** * Returns true if the lock has any of the {@link * arlut.csd.ganymede.server.DBObjectBase DBObjectBase} objects in * the provided List locked. */ boolean overlaps(List<DBObjectBase> bases) { synchronized (lockSync) { return arlut.csd.Util.VectorUtils.overlaps(bases, getBases()); } } /** * Returns true if this lock is locked. */ abstract public boolean isLocked(); /** * Returns true if this lock is waiting in establish() */ abstract public boolean isEstablishing(); /** * Returns true if this lock is aborting */ abstract public boolean isAborting(); /** * Returns immutable list of DBObjectBases that this lock is meant * to cover. */ abstract List<DBObjectBase> getBases(); /** * <p>This method waits until the lock can be established. The * {@link arlut.csd.ganymede.server.DBObjectBase DBObjectBases} locked * are specified in the constructor of the implementation subclass * ({@link arlut.csd.ganymede.server.DBReadLock DBReadLock}, * {@link arlut.csd.ganymede.server.DBWriteLock DBWriteLock}, or * {@link arlut.csd.ganymede.server.DBDumpLock DBDumpLock}).</p> * * <p>A thread that calls establish() will be suspended (waiting on * the server's {@link arlut.csd.ganymede.server.DBLockSync * DBLockSync} until all DBObjectBases listed in the DBLock's * constructor are available to be locked. At that point, the * thread blocking on establish() will wake up possessing a lock on * the requested DBObjectBases.</p> * * <p>It is possible for the establish() to fail completely.. the * admin console may reject a client whose thread is blocking on * establish(), for instance, or the server may be shut down. In * those cases, another thread may call the DBLock's * {@link arlut.csd.ganymede.server.DBLock#abort() abort()} method, in * which case * establish() will throw an InterruptedException, and the lock will * not be established.</p> * * <p>The possessors of DBLocks are identified by a key Object that * is provided on the call to {@link * arlut.csd.ganymede.server.DBLock#establish(java.lang.Object)}. A * given key may only have one DBWriteLock established at a time, * but it may have multiple concurrent DBDumpLocks and DBReadLocks * established if there are no DBWriteLocks held by that key or * locked on DBObjectBases that overlap this lock request.</p> * * @param key An object used in the server to uniquely identify the * entity internal to Ganymede that is attempting to obtain the * lock, typically a unique String. */ abstract void establish(Object key) throws InterruptedException; /** * Unlock the bases held by this lock. */ abstract void release(); /** * Abort this lock; if any thread is waiting in establish() on this * lock when abort() is called, that thread's call to establish() * will fail with an InterruptedException. */ abstract void abort(); /** * Returns the key that this lock is established with, or null if * the lock has not been established. */ abstract Object getKey(); /** * Returns a string describing this lock for use in debug messages */ public String toString() { StringBuilder returnString = new StringBuilder(); // get the object's type and ID returnString.append(super.toString()); Object key = this.getKey(); if (key != null) { returnString.append(", key = "); returnString.append(key.toString()); } else { returnString.append(", key = null"); } if (isEstablishing()) { returnString.append(", establishing"); } if (isAborting()) { returnString.append(", aborted"); } if (isLocked()) { returnString.append(", locked on: "); } else { returnString.append(", currently unlocked on: "); } List<DBObjectBase> bases = getBases(); for (int i = 0; i < bases.size(); i++) { if (i>0) { returnString.append(", "); } returnString.append(bases.get(i).toString()); } return returnString.toString(); } /** * Utility method used when debugging is enabled in subclasses. */ String getBaseNames(List<DBObjectBase> bases) { StringBuilder buf = new StringBuilder(); for (DBObjectBase base: bases) { buf.append("\n\t\t\t"); buf.append(base.getName()); } buf.append("\n"); return buf.toString(); } }