/* GASH 2 DBReadLock.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.ArrayList; import java.util.Collections; import java.util.List; /*------------------------------------------------------------------------------ class DBReadLock ------------------------------------------------------------------------------*/ /** * <p>DBReadLock is a class used in the Ganymede server to represent a * read lock on one or more {@link * arlut.csd.ganymede.server.DBObjectBase DBObjectBase} objects. A * DBReadLock is used in the {@link * arlut.csd.ganymede.server.GanymedeSession GanymedeSession} class to * guarantee that all query operations go from start to finish without * any changes being made along the way.</p> * * <p>While a DBReadLock is established on a DBObjectBase, no changes * may be made to that base. The {@link * arlut.csd.ganymede.server.DBWriteLock DBWriteLock}'s {@link * arlut.csd.ganymede.server.DBWriteLock#establish(java.lang.Object) * establish()} method will suspend until all read locks on a base are * cleared. As soon as a thread attempts to establish a DBWriteLock * on a base, no more DBReadLocks will be established on that base * until the DBWriteLock is cleared, but any DBReadLocks already * established will persist until released, whereupon the DBWriteLock * will establish.</p> * * <p>{@link arlut.csd.ganymede.server.DBDumpLock DBDumpLocks} can be * established while a DBReadLocks is active and vice-versa, * though.</p> * * <p>See {@link arlut.csd.ganymede.server.DBLock DBLock}, {@link * arlut.csd.ganymede.server.DBWriteLock DBWriteLock}, and {@link * arlut.csd.ganymede.server.DBDumpLock DBDumpLock} for details.</p> */ public final class DBReadLock extends DBLock { static final boolean debug = false; /* -- */ /** * All DBLock's have an identifier key, which is used to identify * the lock in the {@link arlut.csd.ganymede.server.DBStore * DBStore}'s {@link arlut.csd.ganymede.server.DBLockSync * DBLockSync} object. The establish() methods in the DBLock * subclasses consult the DBStore.lockSync to make sure that no * {@link arlut.csd.ganymede.server.DBSession DBSession} ever * possesses more than one write lock, to prevent deadlocks from * occuring in the server. */ private Object key; private boolean locked = false; private boolean inEstablish = false; private boolean abort = false; /** * In order to prevent deadlocks, each individual lock must be * established on all applicable {@link * arlut.csd.ganymede.server.DBObjectBase DBObjectBases} at the time * the lock is initially established. baseSet is the List of * DBObjectBases that this DBLock is/will be locked on. */ private final List<DBObjectBase> baseSet; /** * Constructor to get a shared read lock on all of the server's * object bases */ public DBReadLock(DBStore store) { super(store.lockSync); this.baseSet = Collections.unmodifiableList(new ArrayList(store.getBases())); } /** * Constructor to get a shared read lock on a subset of the object * bases. */ public DBReadLock(DBStore store, List<DBObjectBase> baseSet) { super(store.lockSync); this.baseSet = Collections.unmodifiableList(new ArrayList(baseSet)); } /** * Returns true if this lock is locked. */ @Override public final boolean isLocked() { return this.locked; } /** * Returns true if this lock is waiting in establish() */ @Override public final boolean isEstablishing() { return this.inEstablish; } /** * Returns true if this lock is aborting */ @Override public final boolean isAborting() { return this.abort; } /** * Returns immutable list of DBObjectBases that this lock is meant * to cover. */ @Override final List<DBObjectBase> getBases() { return this.baseSet; } @Override final Object getKey() { if (isLocked()) { return key; } else { return null; } } /** * <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 this DBReadLock's * constructor are available to be locked. At that point, the * thread blocking on establish() will wake up possessing a shared * read 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 DBReadLock'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 with 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. */ @Override public final void establish(Object key) throws InterruptedException { boolean okay = false; /* -- */ if (debug) { debug(key, "establish() enter"); Ganymede.printCallStack(); } synchronized (lockSync) { if (!lockSync.claimLockKey(key, this)) { throw new RuntimeException("Error: read lock sought by owner of existing write or dump lockset for key: " + key); } try { lockSync.incLocksWaitingCount(); this.inEstablish = true; this.key = key; while (!okay) { if (debug) debug("establish() looping to get establish permission for " + getBaseNames(baseSet)); if (this.abort) { throw new InterruptedException("DBReadLock (" + key + "): establish aborting before permission granted"); } okay = true; for (DBObjectBase base: baseSet) { // check for writers. we don't care about // dumpers, since we can read without problems // while a dump lock is held if (base.hasWriter()) { if (debug) debug("establish() base " + base.getName() + " has writers queued/locked"); okay = false; break; } } if (!okay) { if (debug) debug("establish() waiting on lockSync"); lockSync.wait(2500); if (debug) debug("establish() done waiting on lockSync"); continue; } // nothing can stop us now for (DBObjectBase base: baseSet) { base.addReader(this); } this.locked = true; lockSync.incLockCount(); if (debug) debug("establish() read lock established"); } } finally { lockSync.decLocksWaitingCount(); this.inEstablish = false; if (!this.locked) { lockSync.unclaimLockKey(key, this); } lockSync.notifyAll(); } } } /** * <p>Relinquish the lock on bases held by this lock object.</p> * * <p>Should be called by {@link arlut.csd.ganymede.server.DBSession * DBSession}'s {@link * arlut.csd.ganymede.server.DBSession#releaseLock(arlut.csd.ganymede.server.DBLock) * releaseLock()} method.</p> * * <p>Note that this method is designed to be able to be called from * one thread while another is trying to use and/or establish the * lock. If this.abort is not set to true before calling release(), * release() will block until the establish is granted. That's why * abort() sets this.abort to true before calling release().</p> * * <p>The point of release() is to clear out this lock's connections * to the locked object bases and to allow DBLock establish() * methods in other threads to proceed.</p> */ @Override public final void release() { if (debug) { debug("release() attempting release"); Ganymede.printCallStack(); } synchronized (lockSync) { // if this lock is being established in another thread, we // need to wait until that thread exits its establish section. // if we haven't set abort to true, this won't happen until it // gets the lock established, or is interrupted while (this.inEstablish) { if (debug) debug("release() looping waiting on inEstablish"); try { lockSync.wait(2500); } catch (InterruptedException ex) { } } if (!locked) { if (debug) debug("release() not locked, returning"); return; } for (DBObjectBase base: baseSet) { base.removeReader(this); } locked = false; lockSync.unclaimLockKey(key, this); if (debug) debug("release() released"); this.key = null; // for gc lockSync.decLockCount(); lockSync.notifyAll(); } } /** * <p>Withdraw this lock. This method can be called by a thread to * interrupt a lock establish that is blocked waiting to get access * to the appropriate set of {@link * arlut.csd.ganymede.server.DBObjectBase DBObjectBase} objects. If * this method is called while another thread is waiting in * establish(), establish() will throw an InterruptedException.</p> * * <p>Once abort() is processed, this lock may never be established. * Any subsequent calls to establish() will always throw * InterruptedException.</p> * * <p>Note that calling abort() on a lock that has already * established in another thread will remove the lock, but a thread * that is using the lock to iterate over a list will explicitly * need to check to see if its lock was pulled.</p> */ @Override public final void abort() { synchronized (lockSync) { if (debug) debug("abort() aborting"); this.abort = true; release(); // blocks until freed } } private void debug(Object key, String message) { System.err.println("DBReadLock(" + key + "): " + message); } private void debug(String message) { System.err.println("DBReadLock(" + this.key + "): " + message); } }