001: /*
002: * @(#)TimedProcess.java
003: *
004: * Copyright (C) 2001,2003 Matt Albrecht
005: * groboclown@users.sourceforge.net
006: * http://groboutils.sourceforge.net
007: *
008: * Permission is hereby granted, free of charge, to any person obtaining a
009: * copy of this software and associated documentation files (the "Software"),
010: * to deal in the Software without restriction, including without limitation
011: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
012: * and/or sell copies of the Software, and to permit persons to whom the
013: * Software is furnished to do so, subject to the following conditions:
014: *
015: * The above copyright notice and this permission notice shall be included in
016: * all copies or substantial portions of the Software.
017: *
018: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
019: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
020: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
021: * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
022: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
023: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
024: * DEALINGS IN THE SOFTWARE.
025: */
026:
027: package net.sourceforge.groboutils.util.thread.v1;
028:
029: /**
030: * A Utility to run a task in a thread, and to kill it if the thread runs
031: * too long. This will first attempt an interrupt() on the thread. If that
032: * doesn't work, then a stop() will be executed. This is a well-documented
033: * bad practice, so you have been warned!
034: *
035: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
036: * @version $Date: 2003/02/10 22:52:49 $
037: * @since 15-Mar-2002
038: */
039: public class TimedProcess {
040: protected static TimedProcess s_instance = new TimedProcess();
041:
042: public static final int REASONABLE_TIME = 500;
043:
044: /**
045: * Implement this interface to have your own method for killing the
046: * runner and thread. The caller will perform the Thread.join() manually.
047: * If the thread is still running after every possible attempt to stop
048: * the thread has occured (run this interface, and join), then a stop()
049: * will be called on the thread.
050: */
051: public static interface RunnableKiller {
052: public void killRunnable(Runnable r, Thread t);
053: }
054:
055: private static class InterruptRunnableKiller implements
056: RunnableKiller {
057: public void killRunnable(Runnable r, Thread t) {
058: t.interrupt();
059: }
060: }
061:
062: private RunnableKiller interruptor = new InterruptRunnableKiller();
063:
064: protected TimedProcess() {
065: // do nothing
066: }
067:
068: public static TimedProcess getInstance() {
069: return s_instance;
070: }
071:
072: /**
073: * Runs the given process in a Thread. If it does not stop within the
074: * specified time, then attempts will be made to kill the thread
075: * (see earlier text). If the process did not complete in the specified
076: * time, an <tt>InterruptedException</tt> will be thrown.
077: *
078: * @exception InterruptedException if the process did not complete within
079: * the given time frame.
080: */
081: public void runTimed(Runnable process, long waitTimeMillis)
082: throws InterruptedException {
083: runTimed(process, waitTimeMillis, this .interruptor);
084: }
085:
086: /**
087: * Runs the given process in a Thread. If it does not stop within the
088: * specified time, then attempts will be made to kill the thread
089: * (see earlier text). If the process did not complete in the specified
090: * time, an <tt>InterruptedException</tt> will be thrown.
091: *
092: * @exception InterruptedException if the process did not complete within
093: * the given time frame.
094: */
095: public void runTimed(Runnable process, long waitTimeMillis,
096: RunnableKiller processEnder) throws InterruptedException {
097: if (processEnder == null || process == null
098: || waitTimeMillis <= 0L) {
099: throw new IllegalArgumentException("no null args");
100: }
101:
102: Thread t = new Thread(process, "TimedProcess");
103: setupThread(t);
104: t.start();
105: joinThread(t, waitTimeMillis);
106: if (t.isAlive()) {
107: stopThread(t, process, processEnder);
108:
109: throw new InterruptedException(
110: "Process did not end within " + waitTimeMillis
111: + " milliseconds.");
112: }
113: }
114:
115: /**
116: *
117: */
118: protected void setupThread(Thread t) {
119: // do nothing
120: }
121:
122: /**
123: *
124: */
125: protected void stopThread(Thread t, Runnable process,
126: RunnableKiller processEnder) {
127: processEnder.killRunnable(process, t);
128: joinThread(t, REASONABLE_TIME);
129: if (t.isAlive()) {
130: // We'll give it one more shot before we really kill it.
131: t.interrupt();
132: joinThread(t, REASONABLE_TIME);
133: if (t.isAlive()) {
134: // ok, that's enough.
135: // Let's do this the hard way.
136: t.stop(); // we know this is bad, but do it anyway
137: if (t.isAlive()) {
138: throw new IllegalStateException(
139: "After stopping the thread, the thread still lives. "
140: + "The thread cannot be killed.");
141: }
142: }
143: }
144: }
145:
146: protected void joinThread(Thread t, long joinTime) {
147: try {
148: t.join(joinTime);
149: } catch (InterruptedException e) {
150: // ignore
151: }
152: }
153: }
|