// jTDS JDBC Driver for Microsoft SQL Server and Sybase
// Copyright (C) 2004 The jTDS Project
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
import java.util.LinkedList;
import java.util.ListIterator;
/**
* Simple timer class used to implement login and query timeouts.
* <p/>
* This thread runs as a Daemon thread to ensure that the java VM will exit
* correctly when normal execution is complete.
* <p/>
* It provides both a singleton implementation and a default constructor for
* the case when more than one timer thread is desired.
*
* @author Alin Sinpalean
* @author Mike Hutchinson
* @version $Id: TimerThread.java,v 1.5 2005/04/28 14:29:31 alin_sinpalean Exp $
*/
public class TimerThread extends Thread {
/**
* Interface to be implemented by classes that request timer services.
*/
public interface TimerListener {
/**
* Event to be fired when the timeout expires.
*/
void timerExpired();
}
/**
* Internal class associating a login or query timeout value with a target
* <code>TimerListener</code>.
*/
private static class TimerRequest {
/** The time when this timeout will expire. */
final long time;
/** Target to notify when the timeout expires. */
final TimerListener target;
/**
* Create a <code>TimerRequest</code>.
*
* @param timeout the desired timeout in milliseconds
* @param target the target object; one of <code>SharedSocket</code> or
* <code>TdsCore</code>
* @throws IllegalArgumentException if the timeout is negative or 0
*/
TimerRequest(int timeout, TimerListener target) {
if (timeout <= 0) {
throw new IllegalArgumentException("Invalid timeout parameter "
+ timeout);
}
this.time = System.currentTimeMillis() + (timeout);
this.target = target;
}
}
/** Singleton instance. */
private static TimerThread instance;
/** List of <code>TimerRequest</code>s to execute, ordered by time. */
private final LinkedList timerList = new LinkedList();
/** Time when the first request time out should occur. */
private long nextTimeout;
/**
* Singleton getter.
*/
public static synchronized TimerThread getInstance() {
if (instance == null) {
instance = new TimerThread();
instance.start();
}
return instance;
}
/**
* Construct a new <code>TimerThread</code> instance.
*/
public TimerThread() {
// Set the thread name
super("jTDS TimerThread");
// Ensure that this thread does not prevent the VM from exiting
this.setDaemon(true);
}
/**
* Execute the <code>TimerThread</code> main loop.
*/
public void run() {
synchronized (timerList) {
while (true) {
try {
try {
// If nextTimeout == 0 (i.e. there are no more requests
// in the queue) wait indefinitely -- wait(0)
timerList.wait(nextTimeout == 0 ? 0
: nextTimeout - System.currentTimeMillis());
} catch (IllegalArgumentException ex) {
// Timeout was negative, fire timeout
}
// Fire expired timeout requests
long time = System.currentTimeMillis();
while (!timerList.isEmpty()) {
// Examime the head of the list and see
// if the timer has expired.
TimerRequest t = (TimerRequest) timerList.getFirst();
if (t.time > time) {
break; // No timers have expired
}
// Notify target of timeout
t.target.timerExpired();
// Remove the fired timeout request
timerList.removeFirst();
}
// Determine next timeout
updateNextTimeout();
} catch (InterruptedException e) {
// nop
}
}
}
}
/**
* Add a timer request to the queue.
* <p/>
* The queue is ordered by time so that the head of the list is always the
* first timer to expire.
*
* @param timeout the interval in milliseconds after which the timer will
* expire
* @param l <code>TimerListener</code> to be notified on timeout
* @return a handle to the timer request, that can later be used with
* <code>cancelTimer</code>
*/
public Object setTimer(int timeout, TimerListener l) {
// Create a new timer request
TimerRequest t = new TimerRequest(timeout, l);
synchronized (timerList) {
if (timerList.isEmpty()) {
// List was empty, just add new request
timerList.add(t);
} else {
// Tiny optimization; new requests will usually go to the end
TimerRequest crt = (TimerRequest) timerList.getLast();
if (t.time >= crt.time) {
timerList.addLast(t);
} else {
// Iterate the list and insert it into the right place
for (ListIterator li = timerList.listIterator(); li.hasNext(); ) {
crt = (TimerRequest) li.next();
if (t.time < crt.time) {
li.previous();
li.add(t);
break;
}
}
}
}
// If this request is now the first in the list, interupt timer
if (timerList.getFirst() == t) {
nextTimeout = t.time;
this.interrupt();
}
}
// Return the created request as timer handle
return t;
}
/**
* Remove a redundant timer before it expires.
*
* @param handle handle to the request to be removed from the queue (a
* <code>TimerRequest</code> instance)
* @return <code>true</code> if timer had not expired
*/
public boolean cancelTimer(Object handle) {
TimerRequest t = (TimerRequest) handle;
synchronized (timerList) {
boolean result = timerList.remove(t);
if (nextTimeout == t.time) {
updateNextTimeout();
}
return result;
}
}
/**
* Check whether a timer has expired.
*
* @param handle handle to the request to be checked for expiry (a
* <code>TimerRequest</code> instance)
* @return <code>true</code> if timer has expired
*/
public boolean hasExpired(Object handle) {
TimerRequest t = (TimerRequest) handle;
synchronized (timerList) {
return !timerList.contains(t);
}
}
/** Internal method that updates the value of {@link #nextTimeout}. */
private void updateNextTimeout() {
nextTimeout = timerList.isEmpty() ? 0
: ((TimerRequest) timerList.getFirst()).time;
}
}
|