001: /*
002: * Copyright (c) 2002-2008 Gargoyle Software Inc. All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * 1. Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: * 2. Redistributions in binary form must reproduce the above copyright notice,
010: * this list of conditions and the following disclaimer in the documentation
011: * and/or other materials provided with the distribution.
012: * 3. The end-user documentation included with the redistribution, if any, must
013: * include the following acknowledgment:
014: *
015: * "This product includes software developed by Gargoyle Software Inc.
016: * (http://www.GargoyleSoftware.com/)."
017: *
018: * Alternately, this acknowledgment may appear in the software itself, if
019: * and wherever such third-party acknowledgments normally appear.
020: * 4. The name "Gargoyle Software" must not be used to endorse or promote
021: * products derived from this software without prior written permission.
022: * For written permission, please contact info@GargoyleSoftware.com.
023: * 5. Products derived from this software may not be called "HtmlUnit", nor may
024: * "HtmlUnit" appear in their name, without prior written permission of
025: * Gargoyle Software Inc.
026: *
027: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
028: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
029: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
030: * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
031: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
032: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
033: * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
034: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
035: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
036: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
037: */
038: package com.gargoylesoftware.htmlunit;
039:
040: import java.util.Collections;
041: import java.util.HashSet;
042: import java.util.Iterator;
043: import java.util.Map;
044: import java.util.Set;
045: import java.util.TreeMap;
046:
047: import org.apache.commons.logging.Log;
048: import org.apache.commons.logging.LogFactory;
049:
050: /**
051: * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br/>
052: *
053: * This is a class that provides thread handling services to internal clients
054: * as well as exposes some of the status of these threads to the public API.
055: *
056: * @version $Revision: 2132 $
057: * @author Brad Clarke
058: * @author Marc Guillemot
059: * @author Daniel Gredler
060: */
061: public class ThreadManager {
062:
063: /** Logging support. */
064: private static final Log LOG = LogFactory
065: .getLog(ThreadManager.class);
066:
067: /** Map of threads, keyed on thread ID. Use a tree map for deterministic iteration. */
068: private Map threadMap_ = Collections.synchronizedMap(new TreeMap());
069:
070: /**
071: * @return The number of tracked threads.
072: */
073: public int activeCount() {
074: return threadMap_.size();
075: }
076:
077: /**
078: * Threads are given numeric IDs in JavaScript and that number is
079: * stored here.
080: */
081: private int nextThreadID_ = 1;
082:
083: /**
084: * HtmlUnit threads are started at a higher priority than the priority
085: * of the first thread to ask for HtmlUnit thread handling services. Assuming
086: * there are no users screwing with their own thread priority after starting
087: * a test this should be enough to encourage background threads to execute
088: * quicker than the test they were started from, if possible.
089: */
090: private static final int PRIORITY = Math.min(Thread.MAX_PRIORITY,
091: Thread.currentThread().getPriority() + 1);
092:
093: /**
094: * Gets the next thread ID in a threadsafe way.
095: * @return the next ID
096: */
097: private synchronized int getNextThreadId() {
098: return nextThreadID_++;
099: }
100:
101: /**
102: * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br/>
103: *
104: * Starts a new job on a background thread. Threads started by this method
105: * are always considered daemon threads and will not prevent the JVM from
106: * shutting down. For our purposes the JUnit test is the only thread that
107: * matters and the background threads are just there to keep the HtmlUnit
108: * objects as up to date as possible.
109: *
110: * @param job The job to start
111: * @param label a job description
112: * @return ID of the new thread, suitable for use in JavaScript and required
113: * when calling {@link #stopThread(int)}
114: */
115: public int startThread(final Runnable job, final String label) {
116: final int myThreadID = getNextThreadId();
117: final Thread newThread = new Thread(job,
118: "HtmlUnit Managed Thread #" + myThreadID + ": " + label) {
119: public void run() {
120: try {
121: super .run();
122: } finally {
123: threadMap_.remove(new Integer(myThreadID));
124: }
125: }
126: };
127: newThread.setPriority(PRIORITY);
128: newThread.setDaemon(true);
129: threadMap_.put(new Integer(myThreadID), newThread);
130: newThread.start();
131: return myThreadID;
132: }
133:
134: /**
135: * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br/>
136: *
137: * Stops a thread that was started in this thread manager.
138: *
139: * Note: this does not immediately stop the thread, only interrupt
140: * it and remove it from being tracked by this manager. The thread
141: * is responsible for handling being interrupted properly and shutting
142: * itself down.
143: *
144: * @param threadID the ID of the thread to stop
145: */
146: public void stopThread(final int threadID) {
147: final Thread thread = (Thread) threadMap_.get(new Integer(
148: threadID));
149: if (thread != null) {
150: thread.interrupt();
151: }
152: }
153:
154: /**
155: * Wait for any executing background threads to complete.
156: *
157: * @param maxWaitMillis The maximum time that should be waited, in milliseconds.
158: * This is not an exact time but will be fairly close.
159: * @return true if all threads expired in the specified time
160: */
161: public boolean joinAll(long maxWaitMillis) {
162: for (Thread t = getNextThread(); t != null && maxWaitMillis > 0; t = getNextThread()) {
163: final long before = System.currentTimeMillis();
164: try {
165: LOG.debug("Trying to join: " + t);
166: t.join(maxWaitMillis);
167: } catch (final InterruptedException e) {
168: throw new RuntimeException("Thread " + t
169: + " interrupted.", e);
170: }
171: maxWaitMillis = maxWaitMillis
172: - (System.currentTimeMillis() - before);
173: }
174: return threadMap_.size() == 0;
175: }
176:
177: /**
178: * Gets the next thread in the thread map in a threadsafe way. This method returns <tt>null</tt>
179: * if there are no more threads in the thread map.
180: *
181: * @return the next thread in the thread map
182: */
183: private Thread getNextThread() {
184: final Thread thread;
185: synchronized (threadMap_) {
186: final Iterator i = threadMap_.values().iterator();
187: if (i.hasNext()) {
188: thread = (Thread) i.next();
189: } else {
190: thread = null;
191: }
192: }
193: return thread;
194: }
195:
196: /**
197: * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br/>
198: * Attempts to stop running threads.
199: */
200: public void interruptAll() {
201: synchronized (threadMap_) {
202: final Set keys = new HashSet(threadMap_.keySet());
203: for (final Iterator i = keys.iterator(); i.hasNext();) {
204: final Integer id = (Integer) i.next();
205: stopThread(id.intValue());
206: }
207: }
208: }
209:
210: /**
211: * {@inheritDoc}
212: */
213: public String toString() {
214: return "ThreadManager: " + threadMap_;
215: }
216:
217: }
|