/* GASH 2 loginSemaphore.java This class provides a handy counting semaphore used to arbitrate user login access to the Ganymede server. Created: 26 January 2000 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2010 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; /*------------------------------------------------------------------------------ class loginSemaphore ------------------------------------------------------------------------------*/ /** * <P>This class provides a handy counting semaphore used to arbitrate user * login access to the {@link arlut.csd.ganymede.server.GanymedeServer GanymedeServer}. * The server uses a single loginSemaphore to safely handle schema edits * and server shutdowns.</P> */ public final class loginSemaphore { static final boolean debug = false; /** * How many users are logged in on this semaphore? */ private int count = 0; /** * If this var is not null, we are disabled. The String here * will hold the reason why. */ private String disableMsg = null; /* -- */ public loginSemaphore() { } /** * <p>disables logins</p> * * <p>This method turns off user logins in Ganymede. A piece of code in the * Ganymede server can call disable() on this object to signal that no further * logins or schema edits should be allowed.</p> * * @param message An explanation of why logins are being disabled. * * @param waitForZero If true, disable may block until the count of users * logged in goes to zero. If false, disable will disable further increments, * but the disable call itself will not block until this time * * @param millis If waitForZero is true, millis controls our blocking behavior. * If millis < 0, we will block as long as necessary. If millis == 0, we will * not block. if millis > 0, we will block no more than that number of milliseconds. * * @return returns null if the disable was successful, or else a descriptive string * if the disable couldn't be carried out */ public synchronized String disable(String message, boolean waitForZero, long millis) throws InterruptedException { if (message == null) { throw new IllegalArgumentException("loginSemaphore error: disable message must != null"); } if (!waitForZero) { if (disableMsg != null) { return "Logins already disabled: " + disableMsg; } else { disableMsg = message; return null; } } if (count == 0 && disableMsg == null) { disableMsg = message; return null; } else if (millis == 0) { if (count != 0) { return "Login count not zero: " + count; } else // disableMsg != null { return "Logins already disabled: " + disableMsg; } } else if (millis < 0) // block indefinitely { while (true) { // we already know from above that we have to wait, so // we'll start the loop waiting wait(); if (count == 0 && disableMsg == null) { disableMsg = message; return null; } } } else // don't block more than millis { long waitTime = millis; long startTime = System.currentTimeMillis(); long timeSoFar = 0; /* -- */ while (true) { // we already know from above that we have to wait, so // we'll start the loop waiting wait(waitTime); if (count == 0 && disableMsg == null) { disableMsg = message; return null; } timeSoFar = System.currentTimeMillis() - startTime; if (timeSoFar > millis) // timed out { return "Timeout"; } else { waitTime = millis - timeSoFar; } } } } /** * <p>re-enables logins.</p> * * @param message Should be identical to the message used to disable * logins, to verify that the right code is doing the re-enabling. * * @exception java.lang.IllegalStateException throws an IllegalStateException * if the message parameter did not match that used to disable the semaphore */ public synchronized void enable(String message) { if (message == null) { throw new IllegalArgumentException("loginSemaphore error: enable message must != null"); } if (message.equals(disableMsg)) { disableMsg = null; notifyAll(); // wake up incrementers } else { throw new IllegalStateException(disableMsg); } } /** * <p>Gated enabled test. If this method returns null, logins are allowed * at the time checkEnabled() is called. This method is to be used by admin * consoles, which should not connect to the server during schema editing or * server shut down, but which should not affect the login count for reasons * of blocking a schema edit disable, say.</p> * * @return null if logins are currently enabled, or a message string if they * are disabled. */ public synchronized String checkEnabled() { return disableMsg; } /** * <p>Returns a count of the number of users logged in on this semaphore</p> */ public int getCount() { return count; } /** * <p>Attempt to increment the login count without blocking/waiting * on failure.</p> * * @return An explanation of why the increment could not be carried * out, or null if the increment was successful. */ public synchronized String increment() { try { return this.increment(0); } catch (InterruptedException ex) { return "noway"; // will never happen, since we incremented with 0 } } /** * <p>Attempt to increment the login count</p> * * @param millis Controls blocking behavior on this call.. if millis < 0, * we will block forever until the semaphore is re-enabled. if millis == 0, * no blocking will be peformed. if millis > 0, we will not block for longer * than that number of milliseconds. * * @return An explanation of why the increment could not be carried out, or null * if the increment was successful. */ public synchronized String increment(long millis) throws InterruptedException { try { if (disableMsg == null) { count++; return null; } if (millis == 0) // don't block.. just fail { return disableMsg; } else if (millis < 0) // block indefinitely { while (true) { // we already know from above that we have to wait, so // we'll start the loop waiting wait(); // can throw InterruptedException if (disableMsg == null) { count++; return null; } } } else // block a limited time { long waitTime = millis; long startTime = System.currentTimeMillis(); long timeSoFar = 0; /* -- */ while (true) { // we already know from above that we have to wait, so // we'll start the loop waiting wait(waitTime); // can throw InterruptedException if (disableMsg == null) { count++; return null; } timeSoFar = System.currentTimeMillis() - startTime; if (timeSoFar > millis) // timed out { return disableMsg; } else { waitTime = millis - timeSoFar; } } } } finally { if (debug) { // get a stack trace for the increment try { throw new RuntimeException("semaphore increment"); } catch (RuntimeException ex) { ex.printStackTrace(); } } } } /** * <p>Decrement the login count.</p> * * <p>This can be done when the semaphore is enabled or disabled, but if * the semaphore count is already zero, an IllegalStateException will * be thrown.</p> */ public synchronized void decrement() { if (count == 0) { throw new IllegalStateException("Error, decrement called on empty loginSemaphore"); } try { count--; notifyAll(); // wake up disablers } finally { if (debug) { // get a stack trace for the increment try { throw new RuntimeException("semaphore decrement"); } catch (RuntimeException ex) { ex.printStackTrace(); } } } } }