/* GASH 2 DBDumpLock.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 DBDumpLock ------------------------------------------------------------------------------*/ /** * <p>DBDumpLock is a {@link arlut.csd.ganymede.server.DBLock DBLock} * object used to lock the {@link arlut.csd.ganymede.server.DBStore * DBStore} either for the purpose of dumping the database or for * handling a GanymedeBuilderTask build. A DBDumpLock establish * request has lower priority than {@link * arlut.csd.ganymede.server.DBWriteLock DBWriteLock} requests, but * once a DBDumpLock establish request is submitted, no new * DBWriteLock can be established until the dumping thread has * completed the dump and released the lock.</p> * * <p>{@link arlut.csd.ganymede.server.DBReadLock DBReadLocks} can be * established while a DBDumpLock is active and vice-versa, * though.</p> * * <p>Essentially, DBDumpLock acts as a DBReadLock that has priority * over incoming {@link arlut.csd.ganymede.server.DBWriteLock * DBWriteLocks}.</p> */ final class DBDumpLock 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 List<DBObjectBase> baseSet; /** * Constructor to get a dump lock on all the object bases. */ public DBDumpLock(DBStore store) { super(store.lockSync); this.baseSet = Collections.unmodifiableList(new ArrayList(store.getBases())); } /** * Constructor to get a dump lock on a subset of the object bases. */ public DBDumpLock(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 DBDumpLock's * constructor are available to be locked. At that point, the * thread blocking on establish() will wake up possessing a shared * dump 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 this DBDumpLock'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. */ @Override public final void establish(Object key) throws InterruptedException { boolean waiting = false; boolean okay = false; /* -- */ if (debug) { debug(key, "establish() entering"); Ganymede.printCallStack(); } synchronized (lockSync) { if (!lockSync.claimLockKey(key, this)) { throw new RuntimeException("Error: dump lock sought by owner of existing lockset."); } try { lockSync.incLocksWaitingCount(); if (debug) debug(key, "added myself to the DBLockSync lockHash."); this.key = key; this.inEstablish = true; // add ourselves to the ObjectBase dump queues.. we don't // have to wait for anything to do this.. it's up to the // writers to hold off on adding themselves to the // writerlists until the dumperlists are empty. for (DBObjectBase base: baseSet) { base.addWaitingDumper(this); } waiting = true; while (!okay) { if (debug) debug("establish() spinning to get establish permission for " + getBaseNames(baseSet)); if (abort) { throw new InterruptedException("DBDumpLock (" + key + "): establish aborting before permission granted"); } okay = true; for (DBObjectBase base: baseSet) { if (base.hasWriter()) { if (debug) debug("establish() blocked on base with writer: " + base.getName()); okay = false; break; } } if (!okay) { lockSync.wait(2500); continue; } // nothing can stop us now for (DBObjectBase base: baseSet) { base.addDumpLock(this); } this.locked = true; lockSync.incLockCount(); } } finally { lockSync.decLocksWaitingCount(); this.inEstablish = false; if (waiting) { for (DBObjectBase base: baseSet) { base.removeWaitingDumper(this); } } if (!this.locked) { lockSync.unclaimLockKey(key, this); this.key = null; } lockSync.notifyAll(); } } if (debug) debug("establish() got the lock."); } /** * Release this lock on all bases locked */ @Override public final void release() { if (debug) { debug("release() entering"); Ganymede.printCallStack(); } synchronized (lockSync) { while (this.inEstablish) { if (debug) debug("release() waiting for inEstablish"); try { lockSync.wait(2500); // or until notify'ed } catch (InterruptedException ex) { } } // note that we have to check locked here or else we might accidentally // release somebody else's lock below if (!this.locked) { if (debug) debug("release() not locked, returning"); return; } for (DBObjectBase base: baseSet) { base.removeDumpLock(this); } this.locked = false; lockSync.unclaimLockKey(key, this); if (debug) debug("release() released"); this.key = null; // gc lockSync.decLockCount(); // notify consoles lockSync.notifyAll(); // many threads might want to check to see what we freed } } /** * <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 blocked in * establish(), establish() will throw an InterruptedException.</p> * * <p>Once abort() is processed, this lock may never be established. * Any subsequent calls to estabish() will always throw * InterruptedException.</p> */ @Override public final void abort() { synchronized (lockSync) { if (debug) debug("abort() aborting"); this.abort = true; release(); } } private void debug(Object key, String message) { System.err.println("DBDumpLock(" + key + "): " + message); } private void debug(String message) { System.err.println("DBDumpLock(" + this.key + "): " + message); } }