001: // jTDS JDBC Driver for Microsoft SQL Server and Sybase
002: // Copyright (C) 2004 The jTDS Project
003: //
004: // This library is free software; you can redistribute it and/or
005: // modify it under the terms of the GNU Lesser General Public
006: // License as published by the Free Software Foundation; either
007: // version 2.1 of the License, or (at your option) any later version.
008: //
009: // This library is distributed in the hope that it will be useful,
010: // but WITHOUT ANY WARRANTY; without even the implied warranty of
011: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: // Lesser General Public License for more details.
013: //
014: // You should have received a copy of the GNU Lesser General Public
015: // License along with this library; if not, write to the Free Software
016: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: //
018: package net.sourceforge.jtds.util;
019:
020: import java.util.LinkedList;
021: import java.util.ListIterator;
022:
023: /**
024: * Simple timer class used to implement login and query timeouts.
025: * <p/>
026: * This thread runs as a Daemon thread to ensure that the java VM will exit
027: * correctly when normal execution is complete.
028: * <p/>
029: * It provides both a singleton implementation and a default constructor for
030: * the case when more than one timer thread is desired.
031: *
032: * @author Alin Sinpalean
033: * @author Mike Hutchinson
034: * @version $Id: TimerThread.java,v 1.5 2005/04/28 14:29:31 alin_sinpalean Exp $
035: */
036: public class TimerThread extends Thread {
037: /**
038: * Interface to be implemented by classes that request timer services.
039: */
040: public interface TimerListener {
041: /**
042: * Event to be fired when the timeout expires.
043: */
044: void timerExpired();
045: }
046:
047: /**
048: * Internal class associating a login or query timeout value with a target
049: * <code>TimerListener</code>.
050: */
051: private static class TimerRequest {
052: /** The time when this timeout will expire. */
053: final long time;
054: /** Target to notify when the timeout expires. */
055: final TimerListener target;
056:
057: /**
058: * Create a <code>TimerRequest</code>.
059: *
060: * @param timeout the desired timeout in milliseconds
061: * @param target the target object; one of <code>SharedSocket</code> or
062: * <code>TdsCore</code>
063: * @throws IllegalArgumentException if the timeout is negative or 0
064: */
065: TimerRequest(int timeout, TimerListener target) {
066: if (timeout <= 0) {
067: throw new IllegalArgumentException(
068: "Invalid timeout parameter " + timeout);
069: }
070: this .time = System.currentTimeMillis() + (timeout);
071: this .target = target;
072: }
073: }
074:
075: /** Singleton instance. */
076: private static TimerThread instance;
077:
078: /** List of <code>TimerRequest</code>s to execute, ordered by time. */
079: private final LinkedList timerList = new LinkedList();
080: /** Time when the first request time out should occur. */
081: private long nextTimeout;
082:
083: /**
084: * Singleton getter.
085: */
086: public static synchronized TimerThread getInstance() {
087: if (instance == null) {
088: instance = new TimerThread();
089: instance.start();
090: }
091: return instance;
092: }
093:
094: /**
095: * Construct a new <code>TimerThread</code> instance.
096: */
097: public TimerThread() {
098: // Set the thread name
099: super ("jTDS TimerThread");
100: // Ensure that this thread does not prevent the VM from exiting
101: this .setDaemon(true);
102: }
103:
104: /**
105: * Execute the <code>TimerThread</code> main loop.
106: */
107: public void run() {
108: synchronized (timerList) {
109: while (true) {
110: try {
111: try {
112: // If nextTimeout == 0 (i.e. there are no more requests
113: // in the queue) wait indefinitely -- wait(0)
114: timerList.wait(nextTimeout == 0 ? 0
115: : nextTimeout
116: - System.currentTimeMillis());
117: } catch (IllegalArgumentException ex) {
118: // Timeout was negative, fire timeout
119: }
120:
121: // Fire expired timeout requests
122: long time = System.currentTimeMillis();
123: while (!timerList.isEmpty()) {
124: // Examime the head of the list and see
125: // if the timer has expired.
126: TimerRequest t = (TimerRequest) timerList
127: .getFirst();
128: if (t.time > time) {
129: break; // No timers have expired
130: }
131: // Notify target of timeout
132: t.target.timerExpired();
133: // Remove the fired timeout request
134: timerList.removeFirst();
135: }
136:
137: // Determine next timeout
138: updateNextTimeout();
139: } catch (InterruptedException e) {
140: // nop
141: }
142: }
143: }
144: }
145:
146: /**
147: * Add a timer request to the queue.
148: * <p/>
149: * The queue is ordered by time so that the head of the list is always the
150: * first timer to expire.
151: *
152: * @param timeout the interval in milliseconds after which the timer will
153: * expire
154: * @param l <code>TimerListener</code> to be notified on timeout
155: * @return a handle to the timer request, that can later be used with
156: * <code>cancelTimer</code>
157: */
158: public Object setTimer(int timeout, TimerListener l) {
159: // Create a new timer request
160: TimerRequest t = new TimerRequest(timeout, l);
161:
162: synchronized (timerList) {
163: if (timerList.isEmpty()) {
164: // List was empty, just add new request
165: timerList.add(t);
166: } else {
167: // Tiny optimization; new requests will usually go to the end
168: TimerRequest crt = (TimerRequest) timerList.getLast();
169: if (t.time >= crt.time) {
170: timerList.addLast(t);
171: } else {
172: // Iterate the list and insert it into the right place
173: for (ListIterator li = timerList.listIterator(); li
174: .hasNext();) {
175: crt = (TimerRequest) li.next();
176: if (t.time < crt.time) {
177: li.previous();
178: li.add(t);
179: break;
180: }
181: }
182: }
183: }
184:
185: // If this request is now the first in the list, interupt timer
186: if (timerList.getFirst() == t) {
187: nextTimeout = t.time;
188: this .interrupt();
189: }
190: }
191:
192: // Return the created request as timer handle
193: return t;
194: }
195:
196: /**
197: * Remove a redundant timer before it expires.
198: *
199: * @param handle handle to the request to be removed from the queue (a
200: * <code>TimerRequest</code> instance)
201: * @return <code>true</code> if timer had not expired
202: */
203: public boolean cancelTimer(Object handle) {
204: TimerRequest t = (TimerRequest) handle;
205:
206: synchronized (timerList) {
207: boolean result = timerList.remove(t);
208: if (nextTimeout == t.time) {
209: updateNextTimeout();
210: }
211: return result;
212: }
213: }
214:
215: /**
216: * Check whether a timer has expired.
217: *
218: * @param handle handle to the request to be checked for expiry (a
219: * <code>TimerRequest</code> instance)
220: * @return <code>true</code> if timer has expired
221: */
222: public boolean hasExpired(Object handle) {
223: TimerRequest t = (TimerRequest) handle;
224:
225: synchronized (timerList) {
226: return !timerList.contains(t);
227: }
228: }
229:
230: /** Internal method that updates the value of {@link #nextTimeout}. */
231: private void updateNextTimeout() {
232: nextTimeout = timerList.isEmpty() ? 0
233: : ((TimerRequest) timerList.getFirst()).time;
234: }
235: }
|